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    }