View Javadoc
1   /*
2    * $Source$
3    * $Revision$
4    *
5    * Copyright (C) 2007 Tim Pizey
6    *
7    * Part of Melati (http://melati.org), a framework for the rapid
8    * development of clean, maintainable web applications.
9    *
10   * Melati is free software; Permission is granted to copy, distribute
11   * and/or modify this software under the terms either:
12   *
13   * a) the GNU General Public License as published by the Free Software
14   *    Foundation; either version 2 of the License, or (at your option)
15   *    any later version,
16   *
17   *    or
18   *
19   * b) any version of the Melati Software License, as published
20   *    at http://melati.org
21   *
22   * You should have received a copy of the GNU General Public License and
23   * the Melati Software License along with this program;
24   * if not, write to the Free Software Foundation, Inc.,
25   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
26   * GNU General Public License and visit http://melati.org to obtain the
27   * Melati Software License.
28   *
29   * Feel free to contact the Developers of Melati (http://melati.org),
30   * if you would like to work out a different arrangement than the options
31   * outlined here.  It is our intention to allow Melati to be used by as
32   * wide an audience as possible.
33   *
34   * This program is distributed in the hope that it will be useful,
35   * but WITHOUT ANY WARRANTY; without even the implied warranty of
36   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37   * GNU General Public License for more details.
38   *
39   * Contact details for copyright holder:
40   *
41   *     Tim Pizey <timp At paneris.org>
42   *     http://paneris.org/~timp
43   */
44  
45  package org.melati.poem;
46  
47  import java.io.ByteArrayOutputStream;
48  import java.io.PrintStream;
49  import java.text.DateFormat;
50  import java.util.Enumeration;
51  import java.util.Map;
52  import java.util.Vector;
53  
54  import org.melati.poem.transaction.Transaction;
55  import org.melati.poem.transaction.Transactioned;
56  import org.melati.poem.util.FlattenedEnumeration;
57  import org.melati.poem.util.MappedEnumeration;
58  
59  /**
60   * The object representing a single table row; this is the <B>PO</B> in POEM!
61   * <p>
62   * Instances are also used to represent selection criteria.
63   *
64   * @author WilliamC At paneris.org
65   */
66  
67  public class JdbcPersistent extends Transactioned implements Persistent, Cloneable {
68    private Table<?> table;
69    private Integer troid;        // null if a floating object
70    private AccessToken clearedToken;
71    private boolean
72        knownCanRead = false, knownCanWrite = false, knownCanDelete = false;
73  
74    /**
75     * Might this object have as yet unsaved modifications?
76     * <p>
77     * This is set to true when a write lock is obtained and this
78     * happens when a value is assigned to a column, except when an
79     * "unsafe" method is used.
80     * <p>
81     * It is set to false when this is written to the database,
82     * even if not yet committed.
83     */
84    private boolean dirty = false;
85  
86    private static final int NONEXISTENT = 0, EXISTENT = 1, DELETED = 2;
87    private int status = NONEXISTENT;
88  
89    private Object[] extras = null;
90    /**
91     * Constructor.
92     */
93    public JdbcPersistent() {
94    }
95  
96    /**
97     * Constructor.
98     * @param table the table of the Persistent
99     * @param troid its Table Row Object Id
100    */
101   public JdbcPersistent(JdbcTable<?> table, Integer troid) {
102     super(table.getDatabase());
103     this.table = table;
104     this.troid = troid;
105   }
106 
107   /**
108    * Constructor.
109    * @param tableName String name of a table
110    * @param troidString String integer representation
111    */
112   public JdbcPersistent(String tableName, String troidString) {
113     super(PoemThread.database());
114     this.table = PoemThread.database().getTable(tableName);
115     this.troid = new Integer(troidString);
116    }
117 
118   // 
119   // --------
120   //  Status
121   // --------
122   // 
123 
124   final void setStatusNonexistent() {
125     status = NONEXISTENT;
126   }
127 
128   final void setStatusExistent() {
129     status = EXISTENT;
130   }
131 
132   /** 
133    * {@inheritDoc}
134    * @see org.melati.poem.Persistent#statusNonexistent()
135    */
136   public final boolean statusNonexistent() {
137     return status == NONEXISTENT;
138   }
139 
140   /** 
141    * {@inheritDoc}
142    * @see org.melati.poem.Persistent#statusExistent()
143    */
144   public final boolean statusExistent() {
145     return status == EXISTENT;
146   }
147 
148   // 
149   // ***************
150   //  Transactioned
151   // ***************
152   // 
153 
154   /**
155    * Throws an exception if this Persistent has a null troid.
156    */
157   private void assertNotFloating() {
158     if (troid == null)
159       throw new InvalidOperationOnFloatingPersistentPoemException(this);
160   }
161 
162   /**
163    * Throws <tt>RowDisappearedPoemException</tt> if this Persistent has a status of DELETED.
164    */
165   private void assertNotDeleted() {
166     if (status == DELETED)
167       throw new RowDisappearedPoemException(this);
168   }
169 
170   /**
171    * Called if not uptodate.
172    * 
173    * {@inheritDoc}
174    * @see org.melati.poem.transaction.Transactioned#load(org.melati.poem.transaction.Transaction)
175    */
176   protected void load(Transaction transaction) {
177     if (troid == null) // I cannot contrive a test to cover this case, but hey
178       throw new InvalidOperationOnFloatingPersistentPoemException(this);
179 
180     table.load((PoemTransaction)transaction, this);
181     // table will clear our dirty flag and set status
182   }
183 
184   /**
185    * Whether we are up to date with respect to current Transaction.
186    * <p>
187    * Return the inherited validity flag.
188    * 
189    * {@inheritDoc}
190    * @see org.melati.poem.transaction.Transactioned#upToDate(org.melati.poem.transaction.Transaction)
191    */
192   protected boolean upToDate(Transaction transaction) {
193     return valid;
194   }
195 
196   /**
197    * Write the persistent to the database if this might be necessary.
198    * <p>
199    * It may be necessary if field values have been set since we last
200    * did a write i.e. this persistent is dirty.
201    * It will not be necessary if this persistent is deleted.
202    * An exception will occur if it does not exist in the database.
203    */
204   protected void writeDown(Transaction transaction) {
205     if (status != DELETED) {
206       assertNotFloating();
207       table.writeDown((PoemTransaction)transaction, this);
208       // table will clear our dirty flag
209     }
210   }
211 
212   /** 
213    * {@inheritDoc}
214    * @see org.melati.poem.transaction.Transactioned#writeLock(org.melati.poem.transaction.Transaction)
215    */
216   protected void writeLock(Transaction transaction) {
217     if (troid != null) {
218       super.writeLock(transaction);
219       assertNotDeleted();
220       dirty = true;
221       table.notifyTouched((PoemTransaction)transaction, this);
222     }
223   }
224 
225   /**
226    * This is just to make this method available to <TT>Table</TT>.
227    */
228   protected void readLock(Transaction transaction) {
229     if (troid != null) {
230       super.readLock(transaction);
231       assertNotDeleted();
232     }
233   }
234 
235   /**
236    * Previously deletion was treated as non-rollbackable, 
237    * as deleteAndCommit was the only deletion mechanism. 
238    * 
239    * {@inheritDoc}
240    * @see org.melati.poem.transaction.Transactioned#commit(org.melati.poem.transaction.Transaction)
241    */
242   protected void commit(Transaction transaction) {
243     //if (status != DELETED) {
244       assertNotFloating();
245       super.commit(transaction);
246     //}
247   }
248 
249   protected void rollback(Transaction transaction) {
250     //if (status != DELETED) {
251     assertNotFloating();
252     if (status == DELETED)
253       status = EXISTENT;
254     super.rollback(transaction);
255     //}
256   }
257 
258   // 
259   // ************
260   //  Persistent
261   // ************
262   // 
263 
264  /** 
265  * {@inheritDoc}
266  * @see org.melati.poem.Persistent#makePersistent()
267  */
268   public void makePersistent() {
269     getTable().create(this);
270   }
271   
272   /* New extra columns could have been added since we were created */
273   synchronized Object[] extras() {
274     if (extras == null)
275       extras = new Object[table.extrasCount()];
276     else if (extras.length < table.extrasCount() ) {
277       Object[] newExtras = new Object[table.extrasCount()];
278       System.arraycopy(extras, 0, newExtras, 0, extras.length);
279       extras = newExtras;
280     }
281     return extras;
282   }
283 
284  /** 
285  * {@inheritDoc}
286  * @see org.melati.poem.Persistent#getTable()
287  */
288   public final Table<?> getTable() {
289     return table;
290   }
291 
292   synchronized void setTable(JdbcTable<?> table, Integer troid) {
293     setTransactionPool(table.getDatabase());
294     this.table = table;
295     this.troid = troid;
296   }
297 
298 
299  /** 
300  * {@inheritDoc}
301  * @see org.melati.poem.Persistent#getDatabase()
302  */
303   public final Database getDatabase() {
304     return table.getDatabase();
305   }
306 
307   /**
308    * @return The Table Row Object Id for this Persistent.
309    * 
310    * FIXME This shouldn't be public because we don't in principle want people
311    * to know even the troid of an object they aren't allowed to read.  However,
312    * I think this information may leak out elsewhere.
313    * To fix is not simple, as generated setters rely upon a lock-free read of the object to set. 
314    * 
315    * {@inheritDoc}
316    * 
317    * @see org.melati.poem.Persistable#troid()
318    */
319   public final Integer troid() {
320     return troid;
321   }
322 
323   /**
324    * The object's troid.
325    *
326    * @return Every record (object) in a POEM database must have a
327    *         troid (table row ID, or table-unique non-nullable integer primary
328    *         key), often but not necessarily called <TT>id</TT>, so that it can
329    *         be conveniently `named' for retrieval.
330    *
331    * @exception AccessPoemException
332    *                if <TT>assertCanRead</TT> fails
333    *
334    * @see Table#getObject(java.lang.Integer)
335    * @see #assertCanRead()
336    */
337 
338   public final Integer getTroid() throws AccessPoemException {
339     assertCanRead();
340     return troid();
341   }
342 
343   // 
344   // ----------------
345   //  Access control
346   // ----------------
347   // 
348 
349   protected void existenceLock(SessionToken sessionToken) {
350     super.readLock(sessionToken.transaction);
351   }
352 
353   protected void readLock(SessionToken sessionToken)
354       throws AccessPoemException {
355     assertCanRead(sessionToken.accessToken);
356     readLock(sessionToken.transaction);
357   }
358 
359   protected void writeLock(SessionToken sessionToken)
360       throws AccessPoemException {
361     if (troid != null)
362       assertCanWrite(sessionToken.accessToken);
363     writeLock(sessionToken.transaction);
364   }
365 
366   protected void deleteLock(SessionToken sessionToken)
367       throws AccessPoemException {
368     if (troid != null)
369       assertCanDelete(sessionToken.accessToken);
370     writeLock(sessionToken.transaction);
371   }
372 
373   /** 
374    * {@inheritDoc}
375    * @see org.melati.poem.Persistent#existenceLock()
376    */
377   public void existenceLock() {
378     existenceLock(PoemThread.sessionToken());
379   }
380 
381   /**
382    * Check if we may read this object and then lock it.
383    * @throws AccessPoemException if current AccessToken does not give read Capability
384    */
385   protected void readLock() throws AccessPoemException {
386     readLock(PoemThread.sessionToken());
387   }
388 
389   /**
390    * Check if we may write to this object and then lock it.
391    * @throws AccessPoemException if current AccessToken does not give write Capability
392    */
393   protected void writeLock() throws AccessPoemException {
394     writeLock(PoemThread.sessionToken());
395   }
396 
397   /**
398    * The capability required for reading the object's field values.  This is
399    * used by <TT>assertCanRead</TT> (unless that's been overridden) to obtain a
400    * <TT>Capability</TT> for comparison against the caller's
401    * <TT>AccessToken</TT>.
402    * <p>
403    * NOTE If a canRead column is defined then it will override this method.
404    *
405    * @return the capability specified by the record's <TT>canread</TT> field, 
406    *         or <TT>null</TT> if it doesn't have one or its value is SQL
407    *         <TT>NULL</TT>
408    *
409    * @see #assertCanRead
410    */
411 
412   protected Capability getCanRead() {
413     return null;
414   }
415 
416   /** 
417    * {@inheritDoc}
418    * @see org.melati.poem.Persistent#assertCanRead(org.melati.poem.AccessToken)
419    */
420 
421   public void assertCanRead(AccessToken token)
422       throws AccessPoemException {
423     // FIXME!!!! this is wrong because token could be stale ...
424     if (!(clearedToken == token && knownCanRead) && troid != null) {
425       Capability canRead = getCanRead();
426       if (canRead == null)
427         canRead = getTable().getDefaultCanRead();
428       if (canRead != null) {
429         if (!token.givesCapability(canRead))
430           throw new ReadPersistentAccessPoemException(this, token, canRead);
431         if (clearedToken != token) {
432           knownCanWrite = false;
433           knownCanDelete = false;
434         }
435         clearedToken = token;
436         knownCanRead = true;
437       }
438     }
439   }
440 
441   /** 
442    * {@inheritDoc}
443    * @see org.melati.poem.Persistent#assertCanRead()
444    */
445   public final void assertCanRead() throws AccessPoemException {
446     assertCanRead(PoemThread.accessToken());
447   }
448 
449   /** 
450    * {@inheritDoc}
451    * @see org.melati.poem.Persistent#getReadable()
452    */
453 
454   public final boolean getReadable() {
455     try {
456       assertCanRead();
457       return true;
458     }
459     catch (AccessPoemException e) {
460       return false;
461     }
462   }
463 
464   /**
465    * The capability required for writing the object's field values.  This is
466    * used by <TT>assertCanWrite</TT> (unless that's been overridden) to obtain 
467    * a <TT>Capability</TT> for comparison against the caller's
468    * <TT>AccessToken</TT>.
469    * <p>
470    * NOTE If a canWrite column is defined then it will override this method.
471    *
472    * @return the capability specified by the record's <TT>canwrite</TT> field,
473    *         or <TT>null</TT> if it doesn't have one or its value is SQL
474    *         <TT>NULL</TT>
475    *
476    * @see #assertCanWrite
477    */
478   protected Capability getCanWrite() {
479     return null;
480   }
481 
482   /** 
483    * {@inheritDoc}
484    * @see org.melati.poem.Persistent#assertCanWrite(org.melati.poem.AccessToken)
485    */
486 
487   public void assertCanWrite(AccessToken token)
488       throws AccessPoemException {
489     // FIXME!!!! this is wrong because token could be stale ...
490     if (!(clearedToken == token && knownCanWrite) && troid != null) {
491       Capability canWrite = getCanWrite();
492       if (canWrite == null)
493         canWrite = getTable().getDefaultCanWrite();
494       if (canWrite != null) {
495         if (!token.givesCapability(canWrite))
496           throw new WritePersistentAccessPoemException(this, token, canWrite);
497         if (clearedToken != token) {
498           knownCanRead = false;
499           knownCanDelete = false;
500         }
501         clearedToken = token;
502         knownCanWrite = true;
503       }
504     }
505   }
506 
507   /** 
508    * {@inheritDoc}
509    * @see org.melati.poem.Persistent#assertCanWrite()
510    */
511   public final void assertCanWrite() throws AccessPoemException {
512     assertCanWrite(PoemThread.accessToken());
513   }
514 
515   /**
516    * The capability required for deleting the object.  This is
517    * used by <TT>assertCanDelete</TT> (unless that's been overridden) 
518    * to obtain a <TT>Capability</TT> for comparison against the caller's
519    * <TT>AccessToken</TT>.
520    * <p>
521    * NOTE If a canDelete column is defined then it will override this method.
522    *
523    * @return the capability specified by the record's <TT>candelete</TT> field,
524    *         or <TT>null</TT> if it doesn't have one or its value is SQL
525    *         <TT>NULL</TT>
526    *
527    * @see #assertCanDelete
528    */
529   protected Capability getCanDelete() {
530     return null;
531   }
532 
533   /** 
534    * {@inheritDoc}
535    * @see org.melati.poem.Persistent#assertCanDelete(org.melati.poem.AccessToken)
536    */
537 
538   public void assertCanDelete(AccessToken token)
539       throws AccessPoemException {
540     // FIXME!!!! this is wrong because token could be stale ...
541     if (!(clearedToken == token && knownCanDelete) && troid != null) {
542       Capability canDelete = getCanDelete();
543       if (canDelete == null)
544         canDelete = getTable().getDefaultCanDelete();
545       if (canDelete != null) {
546         if (!token.givesCapability(canDelete))
547           throw new DeletePersistentAccessPoemException(this, token, canDelete);
548         if (clearedToken != token) {
549           knownCanRead = false;
550           knownCanWrite = false;
551         }
552         clearedToken = token;
553         knownCanDelete = true;
554       }
555     }
556   }
557 
558   /** 
559    * {@inheritDoc}
560    * @see org.melati.poem.Persistent#assertCanDelete()
561    */
562   public final void assertCanDelete() throws AccessPoemException {
563     assertCanDelete(PoemThread.accessToken());
564   }
565 
566   /**
567    * The capability required to select the object.
568    * <p>
569    * Any persistent which has a <tt>canSelect</tt> field will override this method. 
570    * <p>
571    * There is no <code>assertCanSelect()</code> yet because I don't understand
572    * this stale token stuff!
573    *
574    * @return the capability the user needs to select this record
575    * TODO document use-case or delete
576    */
577   protected Capability getCanSelect() {
578     return null;
579   }
580   
581   /** 
582    * {@inheritDoc}
583    * @see org.melati.poem.Persistent#assertCanCreate(org.melati.poem.AccessToken)
584    */
585 
586   public void assertCanCreate(AccessToken token) {
587     Capability canCreate = getTable().getCanCreate();
588     if (canCreate != null && !token.givesCapability(canCreate))
589       throw new CreationAccessPoemException(getTable(), token, canCreate);
590   }
591 
592   /** 
593    * {@inheritDoc}
594    * @see org.melati.poem.Persistent#assertCanCreate()
595    */
596   public final void assertCanCreate() throws AccessPoemException {
597     assertCanCreate(PoemThread.accessToken());
598   }
599 
600 
601   // 
602   // ============================
603   //  Reading and writing fields
604   // ============================
605   // 
606 
607   // 
608   // ------
609   //  Raws
610   // ------
611   // 
612 
613   /** 
614    * {@inheritDoc}
615    * @see org.melati.poem.Persistent#getRaw(java.lang.String)
616    */
617   public Object getRaw(String name)
618       throws NoSuchColumnPoemException, AccessPoemException {
619     return getTable().getColumn(name).getRaw(this);
620   }
621 
622   /** 
623    * {@inheritDoc}
624    * @see org.melati.poem.Persistent#getRawString(java.lang.String)
625    */
626 
627   public final String getRawString(String name)
628       throws AccessPoemException, NoSuchColumnPoemException {
629     Column<?> column = getTable().getColumn(name);
630     return column.getType().stringOfRaw(column.getRaw(this));
631   }
632 
633   /** 
634    * {@inheritDoc}
635    * @see org.melati.poem.Persistent#setRaw(java.lang.String, java.lang.Object)
636    */
637 
638   public void setRaw(String name, Object raw)
639       throws NoSuchColumnPoemException, AccessPoemException,
640              ValidationPoemException {
641     getTable().getColumn(name).setRaw(this, raw);
642   }
643 
644   /** 
645    * {@inheritDoc}
646    * @see org.melati.poem.Persistent#setRawString(java.lang.String, java.lang.String)
647    */
648 
649   public final void setRawString(String name, String string)
650       throws NoSuchColumnPoemException, AccessPoemException,
651              ParsingPoemException, ValidationPoemException {
652     Column<?> column = getTable().getColumn(name);
653     column.setRaw(this, column.getType().rawOfString(string));
654   }
655 
656   // 
657   // --------
658   //  Values
659   // --------
660   // 
661 
662   /** 
663    * {@inheritDoc}
664    * @see org.melati.poem.Persistent#getCooked(java.lang.String)
665    */
666 
667   public Object getCooked(String name)
668       throws NoSuchColumnPoemException, AccessPoemException {
669     return getTable().getColumn(name).getCooked(this);
670   }
671 
672   /** 
673    * {@inheritDoc}
674    * @see org.melati.poem.Persistent#getCookedString(java.lang.String, org.melati.poem.PoemLocale, int)
675    */
676 
677   public final String getCookedString(String name, PoemLocale locale,
678                                      int style)
679       throws NoSuchColumnPoemException, AccessPoemException {
680     Column<?> column = getTable().getColumn(name);
681     return column.getType().stringOfCooked(column.getCooked(this),
682                                           locale, style);
683   }
684 
685   /** 
686    * {@inheritDoc}
687    * @see org.melati.poem.Persistent#setCooked(java.lang.String, java.lang.Object)
688    */
689 
690   public void setCooked(String name, Object cooked)
691       throws NoSuchColumnPoemException, ValidationPoemException,
692              AccessPoemException {
693     getTable().getColumn(name).setCooked(this, cooked);
694   }
695 
696   // 
697   // --------
698   //  Fields
699   // --------
700   // 
701 
702   /** 
703    * {@inheritDoc}
704    * @see org.melati.poem.Persistent#getField(java.lang.String)
705    */
706   public final Field<?> getField(String name)
707       throws NoSuchColumnPoemException, AccessPoemException {
708     return getTable().getColumn(name).asField(this);
709   }
710 
711   /** 
712    * {@inheritDoc}
713    * @see org.melati.poem.Persistent#fieldsOfColumns(java.util.Enumeration)
714    */
715   public Enumeration<Field<?>> fieldsOfColumns(Enumeration<Column<?>> columns) {
716     final JdbcPersistent _this = this;
717     return
718         new MappedEnumeration<Field<?>, Column<?>>(columns) {
719           public Field<?> mapped(Column<?> column) {
720             return column.asField(_this);
721           }
722         };
723   }
724 
725   /** 
726    * {@inheritDoc}
727    * @see org.melati.poem.Persistent#getFields()
728    */
729 
730   public Enumeration<Field<?>> getFields() {
731     return fieldsOfColumns(getTable().columns());
732   }
733 
734   /** 
735    * {@inheritDoc}
736    * @see org.melati.poem.Persistent#getRecordDisplayFields()
737    */
738 
739   public Enumeration<Field<?>> getRecordDisplayFields() {
740     return fieldsOfColumns(getTable().getRecordDisplayColumns());
741   }
742 
743   /** 
744    * {@inheritDoc}
745    * @see org.melati.poem.Persistent#getDetailDisplayFields()
746    */
747   public Enumeration<Field<?>> getDetailDisplayFields() {
748     return fieldsOfColumns(getTable().getDetailDisplayColumns());
749   }
750 
751   /** 
752    * {@inheritDoc}
753    * @see org.melati.poem.Persistent#getSummaryDisplayFields()
754    */
755   public Enumeration<Field<?>> getSummaryDisplayFields() {
756     return fieldsOfColumns(getTable().getSummaryDisplayColumns());
757   }
758 
759   /** 
760    * {@inheritDoc}
761    * @see org.melati.poem.Persistent#getSearchCriterionFields()
762    */
763   public Enumeration<Field<?>> getSearchCriterionFields() {
764     return fieldsOfColumns(getTable().getSearchCriterionColumns());
765   }
766 
767   /** 
768    * {@inheritDoc}
769    * @see org.melati.poem.Persistent#getPrimaryDisplayField()
770    */
771   public Field<?> getPrimaryDisplayField() {
772     return getTable().displayColumn().asField(this);
773   }
774 
775   // 
776   // ==================
777   //  Other operations
778   // ==================
779   // 
780 
781   /** 
782    * {@inheritDoc}
783    * @see org.melati.poem.Persistent#delete(java.util.Map)
784    */
785   public void delete(Map<Column<?>, IntegrityFix> columnToIntegrityFix) {
786     
787     assertNotFloating();
788 
789     deleteLock(PoemThread.sessionToken());
790 
791     Enumeration<Column<?>> columns = getDatabase().referencesTo(getTable());
792     Vector<Enumeration<Persistent>> refEnumerations = new Vector<Enumeration<Persistent>>();
793 
794     while (columns.hasMoreElements()) {
795       Column<?> column = columns.nextElement();
796 
797       IntegrityFix fix;
798       try {
799         fix = columnToIntegrityFix == null ?
800                 null : columnToIntegrityFix.get(column);
801       }
802       catch (ClassCastException e) {
803         throw new AppBugPoemException(
804             "columnToIntegrityFix argument to Persistent.deleteAndCommit " +
805                 "is meant to be a Map from Column to IntegrityFix",
806             e);
807       }
808 
809       if (fix == null)
810         fix = column.getIntegrityFix();
811 
812       if (column.getType() instanceof ReferencePoemType)
813         refEnumerations.addElement(
814           fix.referencesTo(this, column, column.selectionWhereEq(troid()),
815                            columnToIntegrityFix));
816       else if (column.getType() instanceof StringKeyReferencePoemType) {
817         getDatabase().log("Found a StringKeyReferencePoemType " + getName());
818         String keyName = ((StringKeyReferencePoemType)column.getType()).targetKeyName();
819         String keyValue = (String)getRaw(keyName);
820         if (keyValue != null)
821           refEnumerations.addElement(
822               fix.referencesTo(this, column, column.selectionWhereEq(keyValue),
823                                columnToIntegrityFix));
824 
825       } else 
826         throw new UnexpectedExceptionPoemException(
827             "Only expecting Reference or StringKeyReferences");
828     }
829 
830     Enumeration<Persistent> refs = new FlattenedEnumeration<Persistent>(refEnumerations.elements());
831 
832     if (refs.hasMoreElements())
833       throw new DeletionIntegrityPoemException(this, refs);
834 
835     delete_unsafe();
836   }
837 
838   /** 
839    * {@inheritDoc}
840    * @see org.melati.poem.Persistent#delete_unsafe()
841    */
842   public void delete_unsafe() {
843     assertNotFloating();
844     SessionToken sessionToken = PoemThread.sessionToken();
845     deleteLock(sessionToken);
846     try {
847       status = DELETED;
848       table.delete(troid(), sessionToken.transaction);
849     } catch (PoemException e) {
850       status = EXISTENT;
851       throw e;
852     }
853   }
854 
855  
856   /** 
857    * {@inheritDoc}
858    * @see org.melati.poem.Persistent#delete()
859    */
860   public final void delete() {
861     delete(null);
862   }
863 
864   /** 
865    * {@inheritDoc}
866    * @see org.melati.poem.Persistent#deleteAndCommit(java.util.Map)
867    */
868   public void deleteAndCommit(Map<Column<?>, IntegrityFix> integrityFixOfColumn)
869       throws AccessPoemException, DeletionIntegrityPoemException {
870 
871     getDatabase().beginExclusiveLock();
872     try {
873       delete(integrityFixOfColumn);
874       PoemThread.commit();
875     }
876     catch (RuntimeException e) {
877       PoemThread.rollback();
878       throw e;
879     }
880     finally {
881       getDatabase().endExclusiveLock();
882     }
883   }
884 
885   /** 
886    * {@inheritDoc}
887    * @see org.melati.poem.Persistent#deleteAndCommit()
888    */
889   public final void deleteAndCommit()
890       throws AccessPoemException, DeletionIntegrityPoemException {
891     deleteAndCommit(null);
892   }
893 
894   /** 
895    * {@inheritDoc}
896    * @see org.melati.poem.Persistent#duplicated()
897    */
898   public Persistent duplicated() throws AccessPoemException {
899     assertNotFloating();
900     assertNotDeleted();
901     return (JdbcPersistent)clone();
902   }
903 
904   /** 
905    * {@inheritDoc}
906    * @see org.melati.poem.Persistent#duplicatedFloating()
907    */
908   public Persistent duplicatedFloating() throws AccessPoemException {
909     return (JdbcPersistent)clone();
910   }
911 
912   // 
913   // ===========
914   //  Utilities
915   // ===========
916   // 
917 
918   /**
919    * A string briefly describing the object for diagnostic purposes.  The name
920    * of its table and its troid.
921    * {@inheritDoc}
922    * @see java.lang.Object#toString()
923    */
924   public String toString() {
925     if (getTable() == null) {
926        return "null/" + troid();      
927     }
928     return getTable().getName() + "/" + troid();
929   }
930 
931   /** 
932    * {@inheritDoc}
933    * @see org.melati.poem.Persistent#displayString(org.melati.poem.PoemLocale, int)
934    */
935   public String displayString(PoemLocale locale, int style)
936       throws AccessPoemException {
937     Column<?> displayColumn = getTable().displayColumn();
938     if (displayColumn.isTroidColumn() && this.troid == null)
939       return "null";
940     else
941       return displayColumn.getType().stringOfCooked(displayColumn.getCooked(this),
942                                                   locale, style);
943   }
944 
945   /** 
946    * {@inheritDoc}
947    * @see org.melati.poem.Persistent#displayString(org.melati.poem.PoemLocale)
948    */
949   public String displayString(PoemLocale locale) 
950       throws AccessPoemException {
951     return displayString(locale, DateFormat.MEDIUM);
952   }
953   /** 
954    * {@inheritDoc}
955    * @see org.melati.poem.Persistent#displayString()
956    */
957   public String displayString() 
958       throws AccessPoemException {
959     return displayString(PoemLocale.HERE, DateFormat.MEDIUM);
960   }
961 
962   // 
963   // ===============
964   //  Support stuff
965   // ===============
966   // 
967 
968   /**
969    * {@inheritDoc}
970    * @see java.lang.Object#hashCode()
971    */
972   public final int hashCode() {
973     if (troid == null)
974       throw new InvalidOperationOnFloatingPersistentPoemException(this);
975 
976     return getTable().hashCode() + troid().intValue();
977   }
978 
979   /**
980    * {@inheritDoc}
981    * @see java.lang.Object#equals(java.lang.Object)
982    */
983   public final boolean equals(Object object) {
984     if (object == null || !(object instanceof Persistent))
985       return false;
986     else {
987       JdbcPersistent other = (JdbcPersistent)object;
988       return other.troid() == troid() &&
989              other.getTable() == getTable();
990     }
991   }
992 
993   /**
994    * {@inheritDoc}
995    * @see org.melati.poem.transaction.Transactioned#invalidate()
996    */
997   public synchronized void invalidate() {
998     assertNotFloating();
999     super.invalidate();
1000     extras = null;
1001   }
1002 
1003   /**
1004    * {@inheritDoc}
1005    * @see java.lang.Object#clone()
1006    */
1007   protected Object clone() {
1008     // to clone it you have to be able to read it
1009     assertCanRead();
1010     JdbcPersistent it;
1011     try {
1012       it = (JdbcPersistent)super.clone();
1013     }
1014     catch (CloneNotSupportedException e) {
1015       throw new UnexpectedExceptionPoemException(e, "Object no longer supports clone.");
1016     }
1017 
1018     it.extras = (Object[])extras().clone();
1019     it.reset();
1020     it.troid = null;
1021     it.status = NONEXISTENT;
1022 
1023     return it;
1024   }
1025 
1026   /** 
1027    * {@inheritDoc}
1028    * @see org.melati.poem.Persistent#dump()
1029    */
1030   public String dump() {
1031     ByteArrayOutputStream baos = new ByteArrayOutputStream();
1032     PrintStream ps = new PrintStream(baos);
1033     dump(ps);
1034     return baos.toString();
1035   }
1036 
1037   /** 
1038    * {@inheritDoc}
1039    * @see org.melati.poem.Persistent#dump(java.io.PrintStream)
1040    */
1041   public void dump(PrintStream p) {
1042     p.println(getTable().getName() + "/" + troid());
1043     for (Enumeration<Field<?>> f = getRecordDisplayFields(); f.hasMoreElements();) {
1044       p.print("  ");
1045       ((Field<?>)f.nextElement()).dump(p);
1046       p.println();
1047     }
1048   }
1049 
1050   /** 
1051    * {@inheritDoc}
1052    * @see org.melati.poem.Persistent#postWrite()
1053    */
1054   public void postWrite() {
1055   }
1056 
1057   /** 
1058    * {@inheritDoc}
1059    * @see org.melati.poem.Persistent#postInsert()
1060    */
1061   public void postInsert() {
1062   }
1063 
1064   /** 
1065    * {@inheritDoc}
1066    * @see org.melati.poem.Persistent#postModify()
1067    */
1068   public void postModify() {
1069   }
1070 
1071   /** 
1072    * {@inheritDoc}
1073    * @see org.melati.poem.Persistent#preEdit()
1074    */
1075   public void preEdit() {
1076   }
1077 
1078   /** 
1079    * {@inheritDoc}
1080    * @see org.melati.poem.Persistent#postEdit(boolean)
1081    */
1082   public void postEdit(boolean creating) {
1083   }
1084 
1085   // 
1086   // =================================
1087   // Use to Represent Query Constructs
1088   // =================================
1089   //
1090 
1091   /**
1092    * Return a SELECT query to count rows matching criteria represented
1093    * by this object.
1094    *
1095    * @param includeDeleted whether to include soft deleted records
1096    * @param excludeUnselectable Whether to append unselectable exclusion SQL 
1097    * @return an SQL query string
1098    */
1099   protected String countMatchSQL(boolean includeDeleted,
1100                               boolean excludeUnselectable) {
1101     return getTable().countSQL(
1102       fromClause(),
1103       getTable().whereClause(this),
1104       includeDeleted, excludeUnselectable);
1105   }
1106 
1107   /**
1108    * Return an SQL FROM clause for use when selecting rows using criteria
1109    * represented by this object.
1110    * <p>
1111    * By default just the table name is returned, quoted as necessary for
1112    * the DBMS.
1113    * <p>
1114    * Subtypes must ensure the result is compatible with the
1115    * result of {@link Table #appendWhereClause(StringBuffer, JdbcPersistent)}.
1116    * @return an SQL snippet 
1117    */
1118   protected String fromClause() {
1119     return getTable().quotedName();
1120   }
1121 
1122   public Treeable[] getChildren() {
1123     Enumeration<Persistent> refs = getDatabase().referencesTo(this);
1124     Vector<Persistent> v = new Vector<Persistent>();
1125     while (refs.hasMoreElements())
1126       v.addElement(refs.nextElement());
1127     Treeable[] kids;
1128     synchronized (v) {
1129       kids = new Treeable[v.size()];
1130       v.copyInto(kids);
1131     }
1132 
1133     return kids;
1134   }
1135 
1136   /** 
1137    * NOTE This will be overridden if the persistent has a field called <tt>name</tt>. 
1138    * {@inheritDoc}
1139    * @see org.melati.poem.Persistent#getName()
1140    */
1141   public String getName() {
1142     return displayString();
1143   }
1144 
1145   /**
1146    * @return the dirty
1147    */
1148   public boolean isDirty() {
1149     return dirty;
1150   }
1151 
1152   /**
1153    * @param dirty the dirty to set
1154    */
1155   public void setDirty(boolean dirty) {
1156     this.dirty = dirty;
1157   }
1158 
1159 }