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