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 }