Coverage Report - org.melati.poem.JdbcPersistent
 
Classes in this File Line Coverage Branch Coverage Complexity
JdbcPersistent
90%
265/292
84%
90/106
1.839
JdbcPersistent$1
100%
2/2
N/A
1.839
 
 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  8357
   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  8357
   private boolean dirty = false;
 85  
 
 86  
   private static final int NONEXISTENT = 0, EXISTENT = 1, DELETED = 2;
 87  8357
   private int status = NONEXISTENT;
 88  
 
 89  8357
   private Object[] extras = null;
 90  
   /**
 91  
    * Constructor.
 92  
    */
 93  8339
   public JdbcPersistent() {
 94  8339
   }
 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  16
     super(table.getDatabase());
 103  16
     this.table = table;
 104  16
     this.troid = troid;
 105  16
   }
 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  2
     super(PoemThread.database());
 114  2
     this.table = PoemThread.database().getTable(tableName);
 115  2
     this.troid = new Integer(troidString);
 116  2
    }
 117  
 
 118  
   // 
 119  
   // --------
 120  
   //  Status
 121  
   // --------
 122  
   // 
 123  
 
 124  
   final void setStatusNonexistent() {
 125  7320
     status = NONEXISTENT;
 126  7320
   }
 127  
 
 128  
   final void setStatusExistent() {
 129  7090
     status = EXISTENT;
 130  7090
   }
 131  
 
 132  
   /** 
 133  
    * {@inheritDoc}
 134  
    * @see org.melati.poem.Persistent#statusNonexistent()
 135  
    */
 136  
   public final boolean statusNonexistent() {
 137  6011
     return status == NONEXISTENT;
 138  
   }
 139  
 
 140  
   /** 
 141  
    * {@inheritDoc}
 142  
    * @see org.melati.poem.Persistent#statusExistent()
 143  
    */
 144  
   public final boolean statusExistent() {
 145  120785
     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  17544
     if (troid == null)
 159  14
       throw new InvalidOperationOnFloatingPersistentPoemException(this);
 160  17530
   }
 161  
 
 162  
   /**
 163  
    * Throws <tt>RowDisappearedPoemException</tt> if this Persistent has a status of DELETED.
 164  
    */
 165  
   private void assertNotDeleted() {
 166  34511
     if (status == DELETED)
 167  14
       throw new RowDisappearedPoemException(this);
 168  34497
   }
 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  827
     if (troid == null) // I cannot contrive a test to cover this case, but hey
 178  0
       throw new InvalidOperationOnFloatingPersistentPoemException(this);
 179  
 
 180  827
     table.load((PoemTransaction)transaction, this);
 181  
     // table will clear our dirty flag and set status
 182  827
   }
 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  827
     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  3236
     if (status != DELETED) {
 206  2346
       assertNotFloating();
 207  2346
       table.writeDown((PoemTransaction)transaction, this);
 208  
       // table will clear our dirty flag
 209  
     }
 210  3236
   }
 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  119567
     if (troid != null) {
 218  1471
       super.writeLock(transaction);
 219  1469
       assertNotDeleted();
 220  1456
       dirty = true;
 221  1456
       table.notifyTouched((PoemTransaction)transaction, this);
 222  
     }
 223  119552
   }
 224  
 
 225  
   /**
 226  
    * This is just to make this method available to <TT>Table</TT>.
 227  
    */
 228  
   protected void readLock(Transaction transaction) {
 229  52133
     if (troid != null) {
 230  33040
       super.readLock(transaction);
 231  33040
       assertNotDeleted();
 232  
     }
 233  52132
   }
 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  570
       assertNotFloating();
 245  570
       super.commit(transaction);
 246  
     //}
 247  570
   }
 248  
 
 249  
   protected void rollback(Transaction transaction) {
 250  
     //if (status != DELETED) {
 251  9
     assertNotFloating();
 252  9
     if (status == DELETED)
 253  8
       status = EXISTENT;
 254  9
     super.rollback(transaction);
 255  
     //}
 256  9
   }
 257  
 
 258  
   // 
 259  
   // ************
 260  
   //  Persistent
 261  
   // ************
 262  
   // 
 263  
 
 264  
  /** 
 265  
  * {@inheritDoc}
 266  
  * @see org.melati.poem.Persistent#makePersistent()
 267  
  */
 268  
   public void makePersistent() {
 269  159
     getTable().create(this);
 270  150
   }
 271  
   
 272  
   /* New extra columns could have been added since we were created */
 273  
   synchronized Object[] extras() {
 274  420
     if (extras == null)
 275  46
       extras = new Object[table.extrasCount()];
 276  374
     else if (extras.length < table.extrasCount() ) {
 277  0
       Object[] newExtras = new Object[table.extrasCount()];
 278  0
       System.arraycopy(extras, 0, newExtras, 0, extras.length);
 279  0
       extras = newExtras;
 280  
     }
 281  420
     return extras;
 282  
   }
 283  
 
 284  
  /** 
 285  
  * {@inheritDoc}
 286  
  * @see org.melati.poem.Persistent#getTable()
 287  
  */
 288  
   public final Table<?> getTable() {
 289  149328
     return table;
 290  
   }
 291  
 
 292  
   synchronized void setTable(JdbcTable<?> table, Integer troid) {
 293  15896
     setTransactionPool(table.getDatabase());
 294  15896
     this.table = table;
 295  15896
     this.troid = troid;
 296  15896
   }
 297  
 
 298  
 
 299  
  /** 
 300  
  * {@inheritDoc}
 301  
  * @see org.melati.poem.Persistent#getDatabase()
 302  
  */
 303  
   public final Database getDatabase() {
 304  5960
     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  46080
     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  43
     assertCanRead();
 340  43
     return troid();
 341  
   }
 342  
 
 343  
   // 
 344  
   // ----------------
 345  
   //  Access control
 346  
   // ----------------
 347  
   // 
 348  
 
 349  
   protected void existenceLock(SessionToken sessionToken) {
 350  12721
     super.readLock(sessionToken.transaction);
 351  12721
   }
 352  
 
 353  
   protected void readLock(SessionToken sessionToken)
 354  
       throws AccessPoemException {
 355  46128
     assertCanRead(sessionToken.accessToken);
 356  46127
     readLock(sessionToken.transaction);
 357  46126
   }
 358  
 
 359  
   protected void writeLock(SessionToken sessionToken)
 360  
       throws AccessPoemException {
 361  119321
     if (troid != null)
 362  1225
       assertCanWrite(sessionToken.accessToken);
 363  119320
     writeLock(sessionToken.transaction);
 364  119319
   }
 365  
 
 366  
   protected void deleteLock(SessionToken sessionToken)
 367  
       throws AccessPoemException {
 368  247
     if (troid != null)
 369  247
       assertCanDelete(sessionToken.accessToken);
 370  247
     writeLock(sessionToken.transaction);
 371  233
   }
 372  
 
 373  
   /** 
 374  
    * {@inheritDoc}
 375  
    * @see org.melati.poem.Persistent#existenceLock()
 376  
    */
 377  
   public void existenceLock() {
 378  6111
     existenceLock(PoemThread.sessionToken());
 379  6111
   }
 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  46128
     readLock(PoemThread.sessionToken());
 387  46126
   }
 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  119321
     writeLock(PoemThread.sessionToken());
 395  119319
   }
 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  321
     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  399
     if (!(clearedToken == token && knownCanRead) && troid != null) {
 425  325
       Capability canRead = getCanRead();
 426  325
       if (canRead == null)
 427  322
         canRead = getTable().getDefaultCanRead();
 428  325
       if (canRead != null) {
 429  9
         if (!token.givesCapability(canRead))
 430  4
           throw new ReadPersistentAccessPoemException(this, token, canRead);
 431  5
         if (clearedToken != token) {
 432  5
           knownCanWrite = false;
 433  5
           knownCanDelete = false;
 434  
         }
 435  5
         clearedToken = token;
 436  5
         knownCanRead = true;
 437  
       }
 438  
     }
 439  395
   }
 440  
 
 441  
   /** 
 442  
    * {@inheritDoc}
 443  
    * @see org.melati.poem.Persistent#assertCanRead()
 444  
    */
 445  
   public final void assertCanRead() throws AccessPoemException {
 446  54
     assertCanRead(PoemThread.accessToken());
 447  52
   }
 448  
 
 449  
   /** 
 450  
    * {@inheritDoc}
 451  
    * @see org.melati.poem.Persistent#getReadable()
 452  
    */
 453  
 
 454  
   public final boolean getReadable() {
 455  
     try {
 456  2
       assertCanRead();
 457  1
       return true;
 458  
     }
 459  1
     catch (AccessPoemException e) {
 460  1
       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  921
     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  1235
     if (!(clearedToken == token && knownCanWrite) && troid != null) {
 491  922
       Capability canWrite = getCanWrite();
 492  922
       if (canWrite == null)
 493  921
         canWrite = getTable().getDefaultCanWrite();
 494  922
       if (canWrite != null) {
 495  161
         if (!token.givesCapability(canWrite))
 496  5
           throw new WritePersistentAccessPoemException(this, token, canWrite);
 497  156
         if (clearedToken != token) {
 498  154
           knownCanRead = false;
 499  154
           knownCanDelete = false;
 500  
         }
 501  156
         clearedToken = token;
 502  156
         knownCanWrite = true;
 503  
       }
 504  
     }
 505  1230
   }
 506  
 
 507  
   /** 
 508  
    * {@inheritDoc}
 509  
    * @see org.melati.poem.Persistent#assertCanWrite()
 510  
    */
 511  
   public final void assertCanWrite() throws AccessPoemException {
 512  4
     assertCanWrite(PoemThread.accessToken());
 513  2
   }
 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  122
     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  255
     if (!(clearedToken == token && knownCanDelete) && troid != null) {
 542  126
       Capability canDelete = getCanDelete();
 543  126
       if (canDelete == null)
 544  122
         canDelete = getTable().getDefaultCanDelete();
 545  126
       if (canDelete != null) {
 546  122
         if (!token.givesCapability(canDelete))
 547  4
           throw new DeletePersistentAccessPoemException(this, token, canDelete);
 548  118
         if (clearedToken != token) {
 549  96
           knownCanRead = false;
 550  96
           knownCanWrite = false;
 551  
         }
 552  118
         clearedToken = token;
 553  118
         knownCanDelete = true;
 554  
       }
 555  
     }
 556  251
   }
 557  
 
 558  
   /** 
 559  
    * {@inheritDoc}
 560  
    * @see org.melati.poem.Persistent#assertCanDelete()
 561  
    */
 562  
   public final void assertCanDelete() throws AccessPoemException {
 563  4
     assertCanDelete(PoemThread.accessToken());
 564  2
   }
 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  0
     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  6021
     Capability canCreate = getTable().getCanCreate();
 588  6021
     if (canCreate != null && !token.givesCapability(canCreate))
 589  4
       throw new CreationAccessPoemException(getTable(), token, canCreate);
 590  6017
   }
 591  
 
 592  
   /** 
 593  
    * {@inheritDoc}
 594  
    * @see org.melati.poem.Persistent#assertCanCreate()
 595  
    */
 596  
   public final void assertCanCreate() throws AccessPoemException {
 597  4
     assertCanCreate(PoemThread.accessToken());
 598  2
   }
 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  57
     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  22
     Column<?> column = getTable().getColumn(name);
 630  22
     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  6
     getTable().getColumn(name).setRaw(this, raw);
 642  6
   }
 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  4
     Column<?> column = getTable().getColumn(name);
 653  4
     column.setRaw(this, column.getType().rawOfString(string));
 654  4
   }
 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  62
     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  8
     Column<?> column = getTable().getColumn(name);
 681  8
     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  67
     getTable().getColumn(name).setCooked(this, cooked);
 694  67
   }
 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  8
     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  28
     final JdbcPersistent _this = this;
 717  28
     return
 718  168
         new MappedEnumeration<Field<?>, Column<?>>(columns) {
 719  
           public Field<?> mapped(Column<?> column) {
 720  140
             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  4
     return fieldsOfColumns(getTable().columns());
 732  
   }
 733  
 
 734  
   /** 
 735  
    * {@inheritDoc}
 736  
    * @see org.melati.poem.Persistent#getRecordDisplayFields()
 737  
    */
 738  
 
 739  
   public Enumeration<Field<?>> getRecordDisplayFields() {
 740  16
     return fieldsOfColumns(getTable().getRecordDisplayColumns());
 741  
   }
 742  
 
 743  
   /** 
 744  
    * {@inheritDoc}
 745  
    * @see org.melati.poem.Persistent#getDetailDisplayFields()
 746  
    */
 747  
   public Enumeration<Field<?>> getDetailDisplayFields() {
 748  2
     return fieldsOfColumns(getTable().getDetailDisplayColumns());
 749  
   }
 750  
 
 751  
   /** 
 752  
    * {@inheritDoc}
 753  
    * @see org.melati.poem.Persistent#getSummaryDisplayFields()
 754  
    */
 755  
   public Enumeration<Field<?>> getSummaryDisplayFields() {
 756  2
     return fieldsOfColumns(getTable().getSummaryDisplayColumns());
 757  
   }
 758  
 
 759  
   /** 
 760  
    * {@inheritDoc}
 761  
    * @see org.melati.poem.Persistent#getSearchCriterionFields()
 762  
    */
 763  
   public Enumeration<Field<?>> getSearchCriterionFields() {
 764  2
     return fieldsOfColumns(getTable().getSearchCriterionColumns());
 765  
   }
 766  
 
 767  
   /** 
 768  
    * {@inheritDoc}
 769  
    * @see org.melati.poem.Persistent#getPrimaryDisplayField()
 770  
    */
 771  
   public Field<?> getPrimaryDisplayField() {
 772  1
     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  134
     assertNotFloating();
 788  
 
 789  126
     deleteLock(PoemThread.sessionToken());
 790  
 
 791  115
     Enumeration<Column<?>> columns = getDatabase().referencesTo(getTable());
 792  115
     Vector<Enumeration<Persistent>> refEnumerations = new Vector<Enumeration<Persistent>>();
 793  
 
 794  291
     while (columns.hasMoreElements()) {
 795  176
       Column<?> column = columns.nextElement();
 796  
 
 797  
       IntegrityFix fix;
 798  
       try {
 799  176
         fix = columnToIntegrityFix == null ?
 800  0
                 null : columnToIntegrityFix.get(column);
 801  
       }
 802  0
       catch (ClassCastException e) {
 803  0
         throw new AppBugPoemException(
 804  
             "columnToIntegrityFix argument to Persistent.deleteAndCommit " +
 805  
                 "is meant to be a Map from Column to IntegrityFix",
 806  
             e);
 807  176
       }
 808  
 
 809  176
       if (fix == null)
 810  176
         fix = column.getIntegrityFix();
 811  
 
 812  176
       if (column.getType() instanceof ReferencePoemType)
 813  338
         refEnumerations.addElement(
 814  169
           fix.referencesTo(this, column, column.selectionWhereEq(troid()),
 815  
                            columnToIntegrityFix));
 816  7
       else if (column.getType() instanceof StringKeyReferencePoemType) {
 817  7
         getDatabase().log("Found a StringKeyReferencePoemType " + getName());
 818  7
         String keyName = ((StringKeyReferencePoemType)column.getType()).targetKeyName();
 819  7
         String keyValue = (String)getRaw(keyName);
 820  7
         if (keyValue != null)
 821  0
           refEnumerations.addElement(
 822  0
               fix.referencesTo(this, column, column.selectionWhereEq(keyValue),
 823  
                                columnToIntegrityFix));
 824  
 
 825  7
       } else 
 826  0
         throw new UnexpectedExceptionPoemException(
 827  
             "Only expecting Reference or StringKeyReferences");
 828  176
     }
 829  
 
 830  115
     Enumeration<Persistent> refs = new FlattenedEnumeration<Persistent>(refEnumerations.elements());
 831  
 
 832  115
     if (refs.hasMoreElements())
 833  1
       throw new DeletionIntegrityPoemException(this, refs);
 834  
 
 835  114
     delete_unsafe();
 836  114
   }
 837  
 
 838  
   /** 
 839  
    * {@inheritDoc}
 840  
    * @see org.melati.poem.Persistent#delete_unsafe()
 841  
    */
 842  
   public void delete_unsafe() {
 843  123
     assertNotFloating();
 844  121
     SessionToken sessionToken = PoemThread.sessionToken();
 845  121
     deleteLock(sessionToken);
 846  
     try {
 847  118
       status = DELETED;
 848  118
       table.delete(troid(), sessionToken.transaction);
 849  0
     } catch (PoemException e) {
 850  0
       status = EXISTENT;
 851  0
       throw e;
 852  118
     }
 853  118
   }
 854  
 
 855  
  
 856  
   /** 
 857  
    * {@inheritDoc}
 858  
    * @see org.melati.poem.Persistent#delete()
 859  
    */
 860  
   public final void delete() {
 861  118
     delete(null);
 862  106
   }
 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  13
     getDatabase().beginExclusiveLock();
 872  
     try {
 873  13
       delete(integrityFixOfColumn);
 874  5
       PoemThread.commit();
 875  
     }
 876  8
     catch (RuntimeException e) {
 877  8
       PoemThread.rollback();
 878  8
       throw e;
 879  
     }
 880  
     finally {
 881  13
       getDatabase().endExclusiveLock();
 882  5
     }
 883  5
   }
 884  
 
 885  
   /** 
 886  
    * {@inheritDoc}
 887  
    * @see org.melati.poem.Persistent#deleteAndCommit()
 888  
    */
 889  
   public final void deleteAndCommit()
 890  
       throws AccessPoemException, DeletionIntegrityPoemException {
 891  7
     deleteAndCommit(null);
 892  3
   }
 893  
 
 894  
   /** 
 895  
    * {@inheritDoc}
 896  
    * @see org.melati.poem.Persistent#duplicated()
 897  
    */
 898  
   public Persistent duplicated() throws AccessPoemException {
 899  4
     assertNotFloating();
 900  2
     assertNotDeleted();
 901  2
     return (JdbcPersistent)clone();
 902  
   }
 903  
 
 904  
   /** 
 905  
    * {@inheritDoc}
 906  
    * @see org.melati.poem.Persistent#duplicatedFloating()
 907  
    */
 908  
   public Persistent duplicatedFloating() throws AccessPoemException {
 909  4
     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  57
     if (getTable() == null) {
 926  2
        return "null/" + troid();      
 927  
     }
 928  55
     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  59
     Column<?> displayColumn = getTable().displayColumn();
 938  59
     if (displayColumn.isTroidColumn() && this.troid == null)
 939  0
       return "null";
 940  
     else
 941  59
       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  2
     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  3
     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  4
     if (troid == null)
 974  2
       throw new InvalidOperationOnFloatingPersistentPoemException(this);
 975  
 
 976  2
     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  32
     if (object == null || !(object instanceof Persistent))
 985  4
       return false;
 986  
     else {
 987  28
       JdbcPersistent other = (JdbcPersistent)object;
 988  28
       return other.troid() == troid() &&
 989  20
              other.getTable() == getTable();
 990  
     }
 991  
   }
 992  
 
 993  
   /**
 994  
    * {@inheritDoc}
 995  
    * @see org.melati.poem.transaction.Transactioned#invalidate()
 996  
    */
 997  
   public synchronized void invalidate() {
 998  14358
     assertNotFloating();
 999  14356
     super.invalidate();
 1000  14356
     extras = null;
 1001  14356
   }
 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  6
     assertCanRead();
 1010  
     JdbcPersistent it;
 1011  
     try {
 1012  6
       it = (JdbcPersistent)super.clone();
 1013  
     }
 1014  0
     catch (CloneNotSupportedException e) {
 1015  0
       throw new UnexpectedExceptionPoemException(e, "Object no longer supports clone.");
 1016  6
     }
 1017  
 
 1018  6
     it.extras = (Object[])extras().clone();
 1019  6
     it.reset();
 1020  6
     it.troid = null;
 1021  6
     it.status = NONEXISTENT;
 1022  
 
 1023  6
     return it;
 1024  
   }
 1025  
 
 1026  
   /** 
 1027  
    * {@inheritDoc}
 1028  
    * @see org.melati.poem.Persistent#dump()
 1029  
    */
 1030  
   public String dump() {
 1031  6
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
 1032  6
     PrintStream ps = new PrintStream(baos);
 1033  6
     dump(ps);
 1034  4
     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  14
     p.println(getTable().getName() + "/" + troid());
 1043  14
     for (Enumeration<Field<?>> f = getRecordDisplayFields(); f.hasMoreElements();) {
 1044  58
       p.print("  ");
 1045  58
       ((Field<?>)f.nextElement()).dump(p);
 1046  54
       p.println();
 1047  
     }
 1048  10
   }
 1049  
 
 1050  
   /** 
 1051  
    * {@inheritDoc}
 1052  
    * @see org.melati.poem.Persistent#postWrite()
 1053  
    */
 1054  
   public void postWrite() {
 1055  6524
   }
 1056  
 
 1057  
   /** 
 1058  
    * {@inheritDoc}
 1059  
    * @see org.melati.poem.Persistent#postInsert()
 1060  
    */
 1061  
   public void postInsert() {
 1062  6010
   }
 1063  
 
 1064  
   /** 
 1065  
    * {@inheritDoc}
 1066  
    * @see org.melati.poem.Persistent#postModify()
 1067  
    */
 1068  
   public void postModify() {
 1069  516
   }
 1070  
 
 1071  
   /** 
 1072  
    * {@inheritDoc}
 1073  
    * @see org.melati.poem.Persistent#preEdit()
 1074  
    */
 1075  
   public void preEdit() {
 1076  2
   }
 1077  
 
 1078  
   /** 
 1079  
    * {@inheritDoc}
 1080  
    * @see org.melati.poem.Persistent#postEdit(boolean)
 1081  
    */
 1082  
   public void postEdit(boolean creating) {
 1083  4
   }
 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  2
     return getTable().countSQL(
 1102  1
       fromClause(),
 1103  1
       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  18
     return getTable().quotedName();
 1120  
   }
 1121  
 
 1122  
   public Treeable[] getChildren() {
 1123  0
     Enumeration<Persistent> refs = getDatabase().referencesTo(this);
 1124  0
     Vector<Persistent> v = new Vector<Persistent>();
 1125  0
     while (refs.hasMoreElements())
 1126  0
       v.addElement(refs.nextElement());
 1127  
     Treeable[] kids;
 1128  0
     synchronized (v) {
 1129  0
       kids = new Treeable[v.size()];
 1130  0
       v.copyInto(kids);
 1131  0
     }
 1132  
 
 1133  0
     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  0
     return displayString();
 1143  
   }
 1144  
 
 1145  
   /**
 1146  
    * @return the dirty
 1147  
    */
 1148  
   public boolean isDirty() {
 1149  8353
     return dirty;
 1150  
   }
 1151  
 
 1152  
   /**
 1153  
    * @param dirty the dirty to set
 1154  
    */
 1155  
   public void setDirty(boolean dirty) {
 1156  14924
     this.dirty = dirty;
 1157  14924
   }
 1158  
 
 1159  
 }