View Javadoc

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