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