001 /*
002 @license.text@
003 */
004 package biz.hammurapi.sql;
005
006 import java.io.Serializable;
007 import java.lang.reflect.Constructor;
008 import java.lang.reflect.InvocationTargetException;
009 import java.sql.PreparedStatement;
010 import java.sql.SQLException;
011 import java.sql.Types;
012 import java.util.AbstractList;
013 import java.util.ArrayList;
014 import java.util.Collection;
015 import java.util.HashMap;
016 import java.util.HashSet;
017 import java.util.Iterator;
018 import java.util.Map;
019 import java.util.Properties;
020 import java.util.Set;
021 import java.util.Map.Entry;
022
023 import javax.xml.transform.TransformerException;
024
025 import org.apache.xpath.CachedXPathAPI;
026 import org.apache.xpath.XPathAPI;
027 import org.w3c.dom.Element;
028 import org.w3c.dom.Node;
029 import org.w3c.dom.traversal.NodeIterator;
030
031 import biz.hammurapi.config.ConfigurationException;
032 import biz.hammurapi.config.Context;
033 import biz.hammurapi.config.ContextConfigurable;
034 import biz.hammurapi.config.DomConfigFactory;
035 import biz.hammurapi.config.DomConfigurable;
036 import biz.hammurapi.convert.CompositeConverter;
037 import biz.hammurapi.sql.columns.Column;
038 import biz.hammurapi.sql.columns.ColumnChangeListener;
039 import biz.hammurapi.util.Attributable;
040 import biz.hammurapi.util.ClassResourceLoader;
041 import biz.hammurapi.util.Observable;
042 import biz.hammurapi.util.Observer;
043 import biz.hammurapi.util.Versioned;
044 import biz.hammurapi.xml.dom.AbstractDomObject;
045 import biz.hammurapi.xml.dom.CompositeDomSerializer;
046 import biz.hammurapi.xml.dom.DomSerializable;
047
048
049 /**
050 * SQLC-generated interface implementations implement this method to achieve
051 * differential update functionality - inserting and updating only modified fields.
052 * @author Pavel Vlasov
053 * @version $Revision: 1.11 $
054 */
055 public class DatabaseObject
056 implements
057 DomSerializable,
058 ColumnChangeListener,
059 Cloneable,
060 ContextConfigurable,
061 Context,
062 DomConfigurable,
063 Attributable,
064 Versioned,
065 Observable,
066 IDatabaseObject,
067 Serializable {
068
069 protected Collection columns=new ArrayList();
070 private Map columnMap=new HashMap();
071 private boolean force;
072 private boolean isDeleted;
073
074 protected Column getColumn(String name) {
075 return (Column) columnMap.get(name);
076 }
077
078 /**
079 * Default constructor
080 */
081 public DatabaseObject() {
082 // Default constructor
083 }
084
085 /**
086 *
087 * @param force Forces columns to be marked as
088 * modified if setter method is invoked even if value being set equals to existing column
089 * value. Useful during inserts with non-nullable columns which map to primitive
090 * types and as such have default values.
091 */
092 public DatabaseObject(boolean force) {
093 this.force=force;
094 }
095
096 protected void addColumn(Column column) {
097 column.setForce(force);
098 columns.add(column);
099 columnMap.put(column.getName(), column);
100 column.setListener(this);
101 }
102
103 /* (non-Javadoc)
104 * @see biz.hammurapi.sql.IDatabaseObject#update(biz.hammurapi.sql.SQLProcessor, java.lang.String)
105 */
106 public int update(SQLProcessor processor, String tableName) throws SQLException {
107 StringBuffer sb=new StringBuffer("UPDATE ");
108 sb.append(tableName);
109 sb.append(" SET ");
110 boolean hasColumns=false;
111 Iterator it=columns.iterator();
112 while (it.hasNext()) {
113 Column column = (Column) it.next();
114 String colName=(column).listName();
115 if (colName!=null) {
116 if (hasColumns) {
117 sb.append(", ");
118 }
119 sb.append(colName);
120 sb.append("=?");
121 hasColumns=true;
122
123 }
124 }
125
126 if (!hasColumns) {
127 return 0;
128 }
129
130 sb.append(" WHERE ");
131
132 boolean hasPkColumns=false;
133 it=columns.iterator();
134 while (it.hasNext()) {
135 Column column = (Column) it.next();
136 if (column.isPrimaryKey()) {
137 String colName=(column).getName();
138 if (hasPkColumns) {
139 sb.append(" AND ");
140 }
141 sb.append(colName);
142 sb.append("=?");
143 hasPkColumns=true;
144 }
145 }
146
147 int ret=processor.processUpdate(sb.toString(), new Parameterizer() {
148 public void parameterize(PreparedStatement ps) throws SQLException {
149 int idx=1;
150 Iterator it=columns.iterator();
151 while (it.hasNext()) {
152 Column column = (Column) it.next();
153 idx=column.parameterize(ps, idx, false);
154 }
155
156 it=columns.iterator();
157 while (it.hasNext()) {
158 Column column = (Column) it.next();
159 if (column.isPrimaryKey()) {
160 column.parameterizeOriginal(ps, idx++);
161 }
162 }
163 }
164 });
165
166 it=columns.iterator();
167 while (it.hasNext()) {
168 ((Column) it.next()).clearModified();
169 }
170
171 originalVersion=objectVersion;
172 isModified=false;
173
174 storeRelationships(processor);
175
176 it=relationships.iterator();
177 while (it.hasNext()) {
178 RelationshipEntry re=(RelationshipEntry) it.next();
179 if (re.name!=null) {
180 Iterator iit=re.items.iterator();
181 while (iit.hasNext()) {
182 IDatabaseObject subItem = (IDatabaseObject) iit.next();
183 if (subItem.isModified()) {
184 re.items.update(processor, subItem);
185 subItem.update(processor, null);
186 }
187 }
188 }
189 }
190 return ret;
191 }
192
193 /* (non-Javadoc)
194 * @see biz.hammurapi.sql.IDatabaseObject#delete(biz.hammurapi.sql.SQLProcessor, java.lang.String)
195 */
196 public int delete(SQLProcessor processor, String tableName) throws SQLException {
197 storeRelationships(processor);
198
199 StringBuffer sb=new StringBuffer("DELETE FROM ");
200 sb.append(tableName);
201 sb.append(" WHERE ");
202
203 boolean hasPkColumns=false;
204 Iterator it=columns.iterator();
205 while (it.hasNext()) {
206 Column column = (Column) it.next();
207 if (column.isPrimaryKey()) {
208 String colName=(column).getName();
209 if (hasPkColumns) {
210 sb.append(" AND ");
211 }
212 sb.append(colName);
213 sb.append("=?");
214 hasPkColumns=true;
215 }
216 }
217
218 int ret=processor.processUpdate(sb.toString(), new Parameterizer() {
219 public void parameterize(PreparedStatement ps) throws SQLException {
220 int idx=1;
221 Iterator it=columns.iterator();
222 while (it.hasNext()) {
223 Column column = (Column) it.next();
224 if (!column.isPrimaryKey()) {
225 idx=column.parameterize(ps, idx, false);
226 }
227 }
228
229 it=columns.iterator();
230 while (it.hasNext()) {
231 Column column = (Column) it.next();
232 if (column.isPrimaryKey()) {
233 idx=column.parameterize(ps, idx, true);
234 }
235 }
236 }
237 });
238
239 it=columns.iterator();
240 while (it.hasNext()) {
241 ((Column) it.next()).clearModified();
242 }
243
244 originalVersion=objectVersion;
245 isDeleted=true;
246 return ret;
247 }
248
249 /* (non-Javadoc)
250 * @see biz.hammurapi.sql.IDatabaseObject#insert(biz.hammurapi.sql.SQLProcessor, java.lang.String)
251 */
252 public int insert(SQLProcessor processor, String tableName) throws SQLException {
253 StringBuffer sb=new StringBuffer("INSERT INTO ");
254 sb.append(tableName);
255 sb.append(" (");
256 int columnsCounter=0;
257 Iterator it=columns.iterator();
258 while (it.hasNext()) {
259 String colName=((Column) it.next()).listName();
260 if (colName!=null) {
261 if (columnsCounter>0) {
262 sb.append(", ");
263 }
264 sb.append(colName);
265 columnsCounter++;
266
267 }
268 }
269
270 if (columnsCounter==0) {
271 return 0;
272 }
273
274 sb.append(") VALUES (");
275 for (int i=0; i<columnsCounter; i++) {
276 if (i>0) {
277 sb.append(", ");
278 }
279 sb.append("?");
280 }
281 sb.append(")");
282
283 int ret=processor.processUpdate(sb.toString(), new Parameterizer() {
284 public void parameterize(PreparedStatement ps) throws SQLException {
285 Iterator it=columns.iterator();
286 for (int i=1; it.hasNext(); i=((Column) it.next()).parameterize(ps, i, false));
287 }
288 });
289
290 it=columns.iterator();
291 while (it.hasNext()) {
292 ((Column) it.next()).clearModified();
293 }
294
295 originalVersion=objectVersion;
296 isModified=false;
297
298 storeRelationships(processor);
299 return ret;
300 }
301
302 /* (non-Javadoc)
303 * @see biz.hammurapi.sql.IDatabaseObject#fromDom(org.w3c.dom.Element)
304 */
305 public void fromDom(Element holder) throws ConfigurationException {
306 fromDom(holder, getNameMap(getClass()));
307 }
308
309 private void loadAttributes(Element holder) throws ConfigurationException {
310 DomConfigFactory dcf=new DomConfigFactory();
311 attributes.clear();
312 try {
313 Node attributesNode = XPathAPI.selectSingleNode(holder, "object-attributes");
314 if (attributesNode!=null) {
315 attributes.putAll((Map) dcf.create(attributesNode));
316 }
317 } catch (TransformerException e) {
318 throw new ConfigurationException("Cannot load database object attributes: "+e, e);
319 }
320
321 }
322
323 /* (non-Javadoc)
324 * @see biz.hammurapi.sql.IDatabaseObject#fromDom(org.w3c.dom.Element, java.util.Properties)
325 */
326 public void fromDom(Element holder, Properties nameMap) throws ConfigurationException {
327 CachedXPathAPI cxpa=new CachedXPathAPI();
328 Iterator it=columns.iterator();
329 while (it.hasNext()) {
330 Column column=(Column) it.next();
331 String nodeName = nameMap.getProperty(column.getName(), column.getName()).trim();
332
333 if (nodeName.length()==0) {
334 // Zero mapping
335 continue;
336 } else if (nodeName.startsWith("@")) {
337 // Attribute mapping
338 if (holder.hasAttribute(nodeName.substring(1))) {
339 column.load(holder.getAttribute(nodeName.substring(1)));
340 }
341 } else if (nodeName.equals(".")) {
342 // Text content mapping
343 column.load(AbstractDomObject.getElementText(holder));
344 } else if (nodeName.startsWith("!")) {
345 // Simple node mapping
346 try {
347 Node cNode=cxpa.selectSingleNode(holder, nodeName.substring(1));
348 if (cNode!=null) {
349 column.load(AbstractDomObject.getElementText(cNode));
350 }
351 } catch (TransformerException e) {
352 throw new ConfigurationException("Cannot load column "+column.getName());
353 }
354 } else {
355 // Regular node mapping
356 try {
357 Node cNode=cxpa.selectSingleNode(holder, nodeName);
358 if (cNode!=null) {
359 column.configure(cNode, null);
360 }
361 } catch (TransformerException e) {
362 throw new ConfigurationException("Cannot load column "+column.getName());
363 }
364 }
365 }
366
367 it=relationships.iterator();
368 while (it.hasNext()) {
369 RelationshipEntry re=(RelationshipEntry) it.next();
370 if (re.name!=null) {
371 String domName=re.domName==null ? re.name : re.domName;
372 try {
373 Constructor constructor=re.items.relationship.getItemType().getConstructor(new Class[] {Element.class, boolean.class});
374 Element rel = ".".equals(domName) ? holder : (Element) cxpa.selectSingleNode(holder, domName);
375 String itemName = re.itemName==null ? "element" : re.itemName;
376 Element itemElement;
377 NodeIterator nit=cxpa.selectNodeIterator(rel, itemName);
378 while ((itemElement=(Element) nit.nextNode())!=null) {
379 re.items.add(constructor.newInstance(new Object[] {itemElement, force ? Boolean.TRUE : Boolean.FALSE}));
380 }
381 } catch (TransformerException e) {
382 throw new ConfigurationException("Cannot load relationship "+re.name, e);
383 } catch (InstantiationException e) {
384 throw new ConfigurationException("Cannot load relationship "+re.name, e);
385 } catch (IllegalAccessException e) {
386 throw new ConfigurationException("Cannot load relationship "+re.name, e);
387 } catch (InvocationTargetException e) {
388 throw new ConfigurationException("Cannot load relationship "+re.name, e);
389 } catch (SecurityException e) {
390 throw new ConfigurationException("Cannot load relationship "+re.name, e);
391 } catch (NoSuchMethodException e) {
392 throw new ConfigurationException("Cannot load relationship "+re.name, e);
393 }
394 }
395 }
396 loadAttributes(holder);
397 }
398
399 public void toDom(Element holder) {
400 toDom(holder, getNameMap(getClass()), false);
401 }
402
403 /* (non-Javadoc)
404 * @see biz.hammurapi.sql.IDatabaseObject#toDom(org.w3c.dom.Element, java.util.Properties, boolean)
405 */
406 public void toDom(Element holder, Properties nameMap, boolean originals) {
407 if (nameMap==null) {
408 nameMap=new Properties();
409 }
410
411 String cna = nameMap.getProperty("@type", "type").trim();
412 if (!"".equals(cna)) {
413 holder.setAttribute(cna, getClass().getName());
414 }
415
416 Iterator it=columns.iterator();
417 while (it.hasNext()) {
418 Column column = (Column) it.next();
419 column.toDom(holder, nameMap.getProperty(column.getName(), column.getName()).trim(), originals);
420 }
421
422 it=relationships.iterator();
423 while (it.hasNext()) {
424 RelationshipEntry re=(RelationshipEntry) it.next();
425 if (re.name!=null) {
426 String domName=re.domName==null ? re.name : re.domName;
427 Element rel = ".".equals(domName) ? holder : holder.getOwnerDocument().createElement(domName);
428 if (rel!=holder) {
429 holder.appendChild(rel);
430 }
431
432 Iterator iit=re.items.iterator();
433 while (iit.hasNext()) {
434 String itemName = re.itemName==null ? "element" : re.itemName;
435 Element ie=holder.getOwnerDocument().createElement(itemName);
436 rel.appendChild(ie);
437 ((DatabaseObject) iit.next()).toDom(ie);
438 }
439 }
440 }
441
442 if (!attributes.isEmpty()) {
443 CompositeDomSerializer
444 .getThreadInstance()
445 .toDomSerializable(attributes)
446 .toDom(AbstractDomObject.addElement(holder, "object-attributes"));
447 }
448 }
449
450 public String toString() {
451 StringBuffer ret=new StringBuffer(getClass().getName());
452 ret.append("[");
453 Iterator it=columns.iterator();
454 while (it.hasNext()) {
455 ret.append(it.next());
456 if (it.hasNext()) {
457 ret.append(", ");
458 }
459 }
460
461 ret.append("]");
462 return ret.toString();
463 }
464
465 /**
466 * Sets modified flag to true and increments version number.
467 * Also broadcasts the change to observers. Changed column is
468 * passed as second argument of update() method.
469 * Override this method in subclasses to react on
470 * change events, but don't forget to invoke
471 * super.onChange().
472 * @param column Changed column
473 */
474 public void onChange(Column column) {
475 isModified=true;
476
477 ++objectVersion;
478
479 if (column.isPrimaryKey()) {
480 isDeleted=false;
481 }
482
483 Iterator it=relationships.iterator();
484 while (it.hasNext()) {
485 Relationship relationship=((RelationshipEntry) it.next()).items.relationship;
486 if (relationship instanceof ColumnChangeListener) {
487 ((ColumnChangeListener) relationship).onChange(column);
488 }
489 }
490
491 synchronized (observers) {
492 it=observers.iterator();
493 while (it.hasNext()) {
494 ((Observer) it.next()).update(this, column);
495 }
496 }
497
498 }
499
500 /* (non-Javadoc)
501 * @see biz.hammurapi.sql.IDatabaseObject#setOriginal()
502 */
503 public void setOriginal() {
504 objectVersion=originalVersion;
505 Iterator it=columns.iterator();
506 while (it.hasNext()) {
507 ((Column) it.next()).setOriginal();
508 }
509 }
510
511 private boolean isModified=false;
512
513 /* (non-Javadoc)
514 * @see biz.hammurapi.sql.IDatabaseObject#isModified()
515 */
516 public boolean isModified() {
517 return isModified;
518 }
519
520 /* (non-Javadoc)
521 * @see biz.hammurapi.sql.IDatabaseObject#isDeleted()
522 */
523 public boolean isDeleted() {
524 return isDeleted;
525 }
526
527 /**
528 * Two objects are considered equal and all their fields are equal.
529 * @param otherBean Other object
530 * @return true if object classes are equal and all member column values are
531 * equal.
532 */
533 public boolean equals(Object otherBean) {
534 if (otherBean==null) {
535 return false;
536 } else if (getClass().equals(otherBean.getClass())) {
537 Collection myColumns=new ArrayList(columns);
538 Collection otherColumns=new ArrayList(((DatabaseObject) otherBean).columns);
539 Iterator mcit=myColumns.iterator();
540 Z:
541 while (mcit.hasNext()) {
542 Column mc=(Column) mcit.next();
543 Iterator ocit=otherColumns.iterator();
544 while (ocit.hasNext()) {
545 Column oc=(Column) ocit.next();
546 if (mc.getName().equals(oc.getName())) {
547 if (mc.equals(oc)) {
548 ocit.remove();
549 mcit.remove();
550 continue Z;
551 }
552
553 return false;
554 }
555 }
556 }
557
558 return myColumns.isEmpty() && otherColumns.isEmpty();
559 } else {
560 return false;
561 }
562 }
563
564 public int hashCode() {
565 int ret=0;
566 Iterator cit=columns.iterator();
567 while (cit.hasNext()) {
568 ret^=cit.next().hashCode();
569 }
570 return ret;
571 }
572
573 /**
574 * Clones object, clears columns collection, clears isDeleted and isModified flags.
575 * Subclasses shall add cloned columns.
576 */
577 public Object clone() throws CloneNotSupportedException {
578 DatabaseObject ret = (DatabaseObject) super.clone();
579 ret.columns.clear();
580 ret.isDeleted=false;
581 ret.isModified=false;
582 return ret;
583 }
584
585 /* (non-Javadoc)
586 * @see biz.hammurapi.sql.IDatabaseObject#clear()
587 */
588 public void clear() {
589 Iterator it = columns.iterator();
590 while (it.hasNext()) {
591 ((Column) it.next()).clear();
592 }
593 isModified=false;
594 isDeleted=false;
595 }
596
597 public void configure(Context context, CompositeConverter converter) throws ConfigurationException {
598 Iterator it = columns.iterator();
599 while (it.hasNext()) {
600 ((Column) it.next()).configure(context, converter);
601 }
602 }
603
604 public Object get(String name) {
605 Column col=(Column) columnMap.get(name);
606 return col==null ? null : col.getObjectValue(false);
607 }
608
609 public void configure(Node configNode, Context context) throws ConfigurationException {
610 fromDom((Element) configNode);
611 }
612
613 private static Map nnMap=new HashMap();
614
615 private static Properties getNameMap(Class clazz) {
616 synchronized (nnMap) {
617 Properties ret=(Properties) nnMap.get(clazz.getName());
618 if (ret==null) {
619 ret=new ClassResourceLoader(clazz).getProperties(null, "namemap");
620 nnMap.put(clazz.getName(), ret);
621 }
622 return ret;
623 }
624 }
625
626 private class RelationshipEntry {
627 String name;
628 String itemName;
629 String domName;
630 RelationshipListImpl items;
631 }
632
633 private class RelationshipListImpl extends AbstractList implements RelationshipList {
634 private ArrayList master=new ArrayList();
635 private Relationship relationship;
636 private boolean alreadyLoaded;
637
638 private RelationshipListImpl(Relationship relationship) {
639 this.relationship=relationship;
640 relationship.setMaster(master);
641 }
642
643 private void update(SQLProcessor processor, IDatabaseObject subItem) throws SQLException {
644 relationship.update(processor, subItem);
645 }
646
647 private void load(SQLProcessor processor) throws SQLException {
648 if (!alreadyLoaded || relationship.isModified()) {
649 master.clear();
650 relationship.load(processor, master);
651 alreadyLoaded=true;
652 }
653 }
654
655 private void store(SQLProcessor processor) throws SQLException {
656 relationship.store(processor);
657 }
658
659 public Object get(int index) {
660 return master.get(index);
661 }
662
663 public int size() {
664 return master.size();
665 }
666
667 public boolean add(Object o) {
668 relationship.add((DatabaseObject) o);
669 return master.add(o);
670 }
671
672 public boolean remove(Object o) {
673 relationship.remove((IDatabaseObject) o);
674 return master.remove(o);
675 }
676
677 public boolean isLazy() {
678 return relationship.isLazy();
679 }
680
681 public boolean isModified() {
682 return relationship.isModified();
683 }
684
685 public IDatabaseObject add() {
686 try {
687 IDatabaseObject item = (IDatabaseObject) relationship.getItemType().newInstance();
688 add(item);
689 return item;
690 } catch (InstantiationException e) {
691 throw new RuntimeException("Cannot instantiate "+relationship.getItemType());
692 } catch (IllegalAccessException e) {
693 throw new RuntimeException("Cannot instantiate "+relationship.getItemType());
694 }
695 }
696 }
697
698 private Collection relationships=new ArrayList();
699 private Map relationshipMap=new HashMap();
700
701 protected RelationshipList getRelationship(String name) {
702 RelationshipEntry entry=(RelationshipEntry) relationshipMap.get(name);
703 if (entry.items.isLazy() || entry.items.isModified()) {
704 try {
705 entry.items.load(((Lazy) this).getProcessor());
706 } catch (SQLException e) {
707 throw new SQLRuntimeException(e);
708 }
709 }
710 return entry.items;
711 }
712
713 /**
714 * @param name Name for composite relationships to render in XML, null for shared relationships.
715 * @param itemName name of item element in XML.
716 * @param itemClass item class. This class shall have constructor from Element,boolean in order to load from
717 * XML docs.
718 * @param relationship
719 */
720 protected RelationshipList addRelationship(String name, String itemName, Relationship relationship) {
721 if (relationship.isLazy() && !(this instanceof Lazy)) {
722 throw new IllegalArgumentException("Cannot add lazy relationship to class which doesn't implement Lazy interface");
723 }
724
725 if (!DatabaseObject.class.isAssignableFrom(relationship.getItemType())) {
726 throw new IllegalArgumentException("Relationship can be established only between DatabaseObject subclasses");
727 }
728 RelationshipEntry entry=new RelationshipEntry();
729 relationships.add(entry);
730 entry.name=name;
731 entry.itemName=itemName;
732 entry.items=new RelationshipListImpl(relationship);
733 Properties nameMap=getNameMap(getClass());
734 String cName = name==null ? null : nameMap.getProperty(name);
735 entry.domName = cName==null ? null : cName.trim();
736 relationshipMap.put(name, entry);
737 return entry.items;
738 }
739
740 /**
741 * Use this method to eagerly load relationships in constructors.
742 * @param processor
743 * @throws SQLException
744 */
745 protected void loadRelationships(SQLProcessor processor) throws SQLException {
746 Iterator it=relationships.iterator();
747 while (it.hasNext()) {
748 RelationshipEntry relationshipEntry = (RelationshipEntry) it.next();
749 if (!relationshipEntry.items.isLazy()) {
750 (relationshipEntry).items.load(processor);
751 }
752 }
753 }
754
755 /**
756 * Use this method to eagerly load relationships in constructors.
757 * @param processor
758 * @throws SQLException
759 */
760 private void storeRelationships(SQLProcessor processor) throws SQLException {
761 Iterator it=relationships.iterator();
762 while (it.hasNext()) {
763 ((RelationshipEntry) it.next()).items.store(processor);
764 }
765 }
766
767
768 /* (non-Javadoc)
769 * @see biz.hammurapi.sql.IDatabaseObject#copy(biz.hammurapi.sql.DatabaseObject)
770 */
771 public void copy(DatabaseObject source) {
772 Iterator it=columns.iterator();
773 while (it.hasNext()) {
774 Column targetColumn=(Column) it.next();
775 Column sourceColumn=(Column) source.columnMap.get(targetColumn.getName());
776 if (targetColumn.getClass().isInstance(sourceColumn)) { // Copy values only from compatible columns
777 targetColumn.set(sourceColumn);
778 }
779 }
780 }
781
782 private Map attributes=new HashMap();
783
784 public void setAttribute(Object key, Object value) {
785 attributes.put(key, value);
786 }
787
788 public Object getAttribute(Object key) {
789 return attributes.get(key);
790 }
791
792 public Object removeAttribute(Object key) {
793 return attributes.remove(key);
794 }
795
796 /* (non-Javadoc)
797 * @see biz.hammurapi.sql.IDatabaseObject#setColumnAttribute(java.lang.String, java.lang.Object, java.lang.Object)
798 */
799 public void setColumnAttribute(String columnName, Object key, Object value) {
800 Column column=(Column) columnMap.get(columnName);
801 if (column==null) {
802 throw new IllegalArgumentException("Column not found: "+columnName);
803 }
804 column.setAttribute(key, value);
805 }
806
807 /* (non-Javadoc)
808 * @see biz.hammurapi.sql.IDatabaseObject#getColumnAttribute(java.lang.String, java.lang.Object)
809 */
810 public Object getColumnAttribute(String columnName, Object key) {
811 Column column=(Column) columnMap.get(columnName);
812 if (column==null) {
813 throw new IllegalArgumentException("Column not found: "+columnName);
814 }
815 return column.getAttribute(key);
816 }
817
818 /* (non-Javadoc)
819 * @see biz.hammurapi.sql.IDatabaseObject#removeColumnAttribute(java.lang.String, java.lang.Object)
820 */
821 public Object removeColumnAttribute(String columnName, Object key) {
822 Column column=(Column) columnMap.get(columnName);
823 if (column==null) {
824 throw new IllegalArgumentException("Column not found: "+columnName);
825 }
826 return column.removeAttribute(key);
827 }
828
829 private static Map sqlTypes=new HashMap();
830
831 /**
832 * Allows to override generated column types with <class name>.sqltypes resource.
833 * Subclasses shall use this method when dealing with Object columns.
834 * @param columnName
835 * @param generatedType
836 * @return
837 */
838 protected int getSqlType(String columnName, int generatedType) {
839 Map typeMap;
840 synchronized (sqlTypes) {
841 String className = getClass().getName();
842 if (sqlTypes.containsKey(className)) {
843 typeMap=(Map) sqlTypes.get(className);
844 } else {
845 Properties literalMap=new ClassResourceLoader(getClass()).getProperties(null, "sqltypes");
846 typeMap=new HashMap();
847 sqlTypes.put(className, typeMap);
848 Iterator it=literalMap.entrySet().iterator();
849 while (it.hasNext()) {
850 Entry entry=(Entry) it.next();
851 try {
852 Integer type = (Integer) Types.class.getDeclaredField((String) entry.getValue()).get(null) ;
853 typeMap.put(entry.getKey(), type);
854 } catch (IllegalArgumentException e) {
855 System.err.println("Invalid SQL Type "+entry.getValue()+", ignored. Cause: "+e);
856 e.printStackTrace();
857 } catch (SecurityException e) {
858 System.err.println("Invalid SQL Type "+entry.getValue()+", ignored. Cause: "+e);
859 e.printStackTrace();
860 } catch (IllegalAccessException e) {
861 System.err.println("Invalid SQL Type "+entry.getValue()+", ignored. Cause: "+e);
862 e.printStackTrace();
863 } catch (NoSuchFieldException e) {
864 System.err.println("Invalid SQL Type "+entry.getValue()+", ignored. Cause: "+e);
865 e.printStackTrace();
866 }
867 }
868 }
869 }
870
871 Integer st=(Integer) typeMap.get(columnName);
872 return st==null ? generatedType : st.intValue();
873 }
874
875 /**
876 * Subclasses can choose to read object version from the database.
877 */
878 protected int objectVersion;
879 protected int originalVersion;
880
881 public int getObjectVersion() {
882 return objectVersion;
883 }
884
885 private Set observers = new HashSet();
886
887 public void addObserver(Observer observer) {
888 synchronized (observers) {
889 observers.add(observer);
890 }
891 }
892
893 public void removeObserver(Observer observer) {
894 synchronized (observers) {
895 observers.remove(observer);
896 }
897 }
898
899 }