Coverage Report - org.apache.ojb.odmg.ObjectEnvelope
 
Classes in this File Line Coverage Branch Coverage Complexity
ObjectEnvelope
N/A
N/A
2.367
 
 1  
 package org.apache.ojb.odmg;
 2  
 
 3  
 /* Copyright 2002-2005 The Apache Software Foundation
 4  
  *
 5  
  * Licensed under the Apache License, Version 2.0 (the "License");
 6  
  * you may not use this file except in compliance with the License.
 7  
  * You may obtain a copy of the License at
 8  
  *
 9  
  *     http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 /**
 18  
  *
 19  
  * @author <a href="mailto:thma@apache.org">Thomas Mahler</a>
 20  
  * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird</a>
 21  
  *
 22  
  */
 23  
 
 24  
 import java.util.HashMap;
 25  
 import java.util.Iterator;
 26  
 import java.util.Map;
 27  
 import java.util.List;
 28  
 import java.util.ArrayList;
 29  
 
 30  
 import org.apache.commons.lang.builder.ToStringBuilder;
 31  
 import org.apache.ojb.broker.Identity;
 32  
 import org.apache.ojb.broker.PersistenceBroker;
 33  
 import org.apache.ojb.broker.PersistenceBrokerException;
 34  
 import org.apache.ojb.broker.OJBRuntimeException;
 35  
 import org.apache.ojb.broker.PersistenceBrokerInternal;
 36  
 import org.apache.ojb.broker.core.proxy.IndirectionHandler;
 37  
 import org.apache.ojb.broker.core.proxy.ProxyHelper;
 38  
 import org.apache.ojb.broker.metadata.ClassDescriptor;
 39  
 import org.apache.ojb.broker.metadata.CollectionDescriptor;
 40  
 import org.apache.ojb.broker.metadata.FieldDescriptor;
 41  
 import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
 42  
 import org.apache.ojb.broker.util.BrokerHelper;
 43  
 import org.apache.ojb.broker.util.ObjectModification;
 44  
 import org.apache.ojb.broker.util.logging.Logger;
 45  
 import org.apache.ojb.broker.util.logging.LoggerFactory;
 46  
 import org.apache.ojb.odmg.states.ModificationState;
 47  
 import org.apache.ojb.odmg.states.StateNewDirty;
 48  
 import org.apache.ojb.odmg.states.StateOldClean;
 49  
 import org.apache.ojb.odmg.states.StateOldDirty;
 50  
 import org.apache.ojb.odmg.link.LinkEntry;
 51  
 import org.apache.ojb.odmg.link.LinkEntryOneToOne;
 52  
 import org.apache.ojb.odmg.link.LinkEntryOneToN;
 53  
 
 54  
 /**
 55  
  * ObjectEnvelope is used during ODMG transactions as a wrapper for a
 56  
  * persistent objects declaration
 57  
  *
 58  
  */
 59  
 public class ObjectEnvelope implements ObjectModification, Image.ImageListener
 60  
 {
 61  
     private Logger log = LoggerFactory.getLogger(ObjectEnvelope.class);
 62  
 
 63  
     static final long serialVersionUID = -829177767933340522L;
 64  
 
 65  
     static final int IS_MATERIALIZED_OBJECT = 11;
 66  
     static final int IS_MATERIALIZED_PROXY = 13;
 67  
     static final int IS_UNMATERIALIZED_PROXY = 17;
 68  
 
 69  
     /**
 70  
      * The objects modification state, e.g. Old and Clean
 71  
      */
 72  
     private ModificationState modificationState = null;
 73  
     private Identity oid;
 74  
     private Boolean hasChanged;
 75  
     private boolean writeLocked;
 76  
 
 77  
     /**
 78  
      * myObj holds the object we are wrapping.
 79  
      */
 80  
     private Object myObj;
 81  
 
 82  
     /**
 83  
      * beforeImage holds a mapping between field
 84  
      * names and values at the start of the transaction.
 85  
      * currentImage holds the mapping at the
 86  
      * end of the transaction.
 87  
      */
 88  
     private Map beforeImage;
 89  
     private Map currentImage;
 90  
     private ObjectEnvelopeTable buffer;
 91  
     // list of all LinkEntry's
 92  
     private List linkEntryList;
 93  
 
 94  
     /**
 95  
      * Create a wrapper by providing an Object.
 96  
      */
 97  
     public ObjectEnvelope(ObjectEnvelopeTable buffer, Identity oid, Object obj, boolean isNewObject)
 98  
     {
 99  
         this.linkEntryList = new ArrayList();
 100  
         this.buffer = buffer;
 101  
         this.oid = oid;
 102  
         // TODO: do we really need to materialize??
 103  
         myObj = ProxyHelper.getRealObject(obj);
 104  
         prepareInitialState(isNewObject);
 105  
         /*
 106  
         TODO: is it possible to improve this? Take care that "new"
 107  
         objects should support "persistence by reachability" too
 108  
         (detection of new/persistent reference objects after maon object lock)
 109  
         */
 110  
         beforeImage = buildObjectImage(getBroker());
 111  
     }
 112  
 
 113  
     public PersistenceBrokerInternal getBroker()
 114  
     {
 115  
         return buffer.getTransaction().getBrokerInternal();
 116  
     }
 117  
 
 118  
     TransactionImpl getTx()
 119  
     {
 120  
         return buffer.getTransaction();
 121  
     }
 122  
 
 123  
     ObjectEnvelopeTable getEnvelopeTable()
 124  
     {
 125  
         return buffer;
 126  
     }
 127  
 
 128  
     public Map getBeforeImage()
 129  
     {
 130  
         if(beforeImage == null)
 131  
         {
 132  
             beforeImage = buildObjectImage(getBroker());
 133  
         }
 134  
         return beforeImage;
 135  
     }
 136  
 
 137  
     public Map getCurrentImage()
 138  
     {
 139  
         if(currentImage == null)
 140  
         {
 141  
             currentImage = buildObjectImage(getBroker());
 142  
         }
 143  
         return currentImage;
 144  
     }
 145  
 
 146  
     /**
 147  
      * This method should be called before transaction ends
 148  
      * to allow cleanup of used resources, e.g. remove proxy listener objects
 149  
      * to avoid invoke of registered objects after tx end.
 150  
      */
 151  
     public void cleanup(boolean reuse, boolean wasInsert)
 152  
     {
 153  
         if(currentImage != null)
 154  
         {
 155  
             performImageCleanup(currentImage, reuse);
 156  
         }
 157  
         if(beforeImage != null)
 158  
         {
 159  
             // we always free all resources of the old image
 160  
             performImageCleanup(beforeImage, false);
 161  
         }
 162  
         if(reuse)
 163  
         {
 164  
             refreshObjectImage(wasInsert);
 165  
         }
 166  
         else
 167  
         {
 168  
             myObj = null;
 169  
         }
 170  
     }
 171  
 
 172  
     private void performImageCleanup(Map imageMap, boolean reuse)
 173  
     {
 174  
         Iterator iterator = imageMap.values().iterator();
 175  
         while(iterator.hasNext())
 176  
         {
 177  
             Image base =  (Image) iterator.next();
 178  
             if(base != null) base.cleanup(reuse);
 179  
         }
 180  
     }
 181  
 
 182  
     private void refreshObjectImage(boolean wasInsert)
 183  
     {
 184  
         try
 185  
         {
 186  
             // if an image already exists we
 187  
             // replace the Identity too, maybe a temporary
 188  
             // used PK value was replaced by the real one,
 189  
             // see in docs SequenceManagerNativeImpl
 190  
             if(getIdentity().isTransient())
 191  
             {
 192  
                 refreshIdentity();
 193  
             }
 194  
             if(currentImage != null)
 195  
             {
 196  
                 beforeImage = currentImage;
 197  
             }
 198  
             else
 199  
             {
 200  
                 if(beforeImage == null)
 201  
                 {
 202  
                     beforeImage = buildObjectImage(getBroker());
 203  
                 }
 204  
             }
 205  
             currentImage = null;
 206  
             hasChanged = null;
 207  
             if(wasInsert)
 208  
             {
 209  
                 /*
 210  
                 on insert we have to replace the PK fields and the version fields, because
 211  
                 they populated after the object was written to DB, thus replace all field image values
 212  
                 */
 213  
                 refreshPKFields();
 214  
             }
 215  
             // TODO: How to handle version fields incremented by the DB?
 216  
             // always refresh the version fields, because these fields will change when written to DB
 217  
             refreshLockingFields();
 218  
         }
 219  
         catch(PersistenceBrokerException e)
 220  
         {
 221  
             beforeImage = null;
 222  
             currentImage = null;
 223  
             hasChanged = null;
 224  
             log.error("Can't refresh object image for " + getIdentity(), e);
 225  
             throw e;
 226  
         }
 227  
     }
 228  
 
 229  
     private void refreshPKFields()
 230  
     {
 231  
         FieldDescriptor[] flds = getClassDescriptor().getPkFields();
 232  
         for(int i = 0; i < flds.length; i++)
 233  
         {
 234  
             FieldDescriptor fld = flds[i];
 235  
             addFieldImage(beforeImage, fld);
 236  
         }
 237  
     }
 238  
 
 239  
     private void refreshLockingFields()
 240  
     {
 241  
         if(getClassDescriptor().isLocking())
 242  
         {
 243  
             FieldDescriptor[] flds = getClassDescriptor().getLockingFields();
 244  
             for(int i = 0; i < flds.length; i++)
 245  
             {
 246  
                 FieldDescriptor fld = flds[i];
 247  
                 addFieldImage(beforeImage, fld);
 248  
             }
 249  
         }
 250  
     }
 251  
 
 252  
     /**
 253  
      * Replace the current with a new generated identity object and
 254  
      * returns the old one.
 255  
      */
 256  
     public Identity refreshIdentity()
 257  
     {
 258  
         Identity oldOid = getIdentity();
 259  
         this.oid = getBroker().serviceIdentity().buildIdentity(myObj);
 260  
         return oldOid;
 261  
     }
 262  
 
 263  
     public Identity getIdentity()
 264  
     {
 265  
         if(oid == null)
 266  
         {
 267  
             oid = getBroker().serviceIdentity().buildIdentity(getObject());
 268  
         }
 269  
         return oid;
 270  
     }
 271  
 
 272  
     /**
 273  
      * Returns the managed materialized object.
 274  
      */
 275  
     public Object getObject()
 276  
     {
 277  
         return myObj;
 278  
     }
 279  
 
 280  
     public Object getRealObject()
 281  
     {
 282  
         return ProxyHelper.getRealObject(getObject());
 283  
     }
 284  
 
 285  
     public void refreshObjectIfNeeded(Object obj)
 286  
     {
 287  
         if(this.myObj != obj)
 288  
         {
 289  
             this.myObj = obj;
 290  
         }
 291  
     }
 292  
 
 293  
     /**
 294  
      * We need to implement the Two-Phase Commit
 295  
      * protocol.
 296  
      *
 297  
      * beginCommit is where we say if we can or cannot
 298  
      * commit the transaction.  At the begining however,
 299  
      * we need to attain the after image so we can isolate
 300  
      * everything.
 301  
      *
 302  
      * We should issue the call against the database
 303  
      * at this point.  If we get a SQL Exception, we
 304  
      * should throw the org.odmg.TransactionAbortedException.
 305  
      *
 306  
      * We should also check to see if the object is
 307  
      * TransactionAware.  If so, we should give it a chance
 308  
      * to kill the transaction before we toss it to the
 309  
      * database.
 310  
      */
 311  
     public void beforeCommit()
 312  
     {
 313  
         if(myObj instanceof TransactionAware)
 314  
         {
 315  
             TransactionAware ta = (TransactionAware) myObj;
 316  
             ta.beforeCommit();
 317  
         }
 318  
     }
 319  
 
 320  
     /**
 321  
      * Method declaration
 322  
      */
 323  
     public void afterCommit()
 324  
     {
 325  
         if(myObj instanceof TransactionAware)
 326  
         {
 327  
             TransactionAware ta = (TransactionAware) myObj;
 328  
             ta.afterCommit();
 329  
         }
 330  
     }
 331  
 
 332  
     /**
 333  
      * Method declaration
 334  
      */
 335  
     public void beforeAbort()
 336  
     {
 337  
         if(myObj instanceof TransactionAware)
 338  
         {
 339  
             TransactionAware ta = (TransactionAware) myObj;
 340  
             ta.beforeAbort();
 341  
         }
 342  
     }
 343  
 
 344  
     /**
 345  
      * Method declaration
 346  
      */
 347  
     public void afterAbort()
 348  
     {
 349  
         if(myObj instanceof TransactionAware)
 350  
         {
 351  
             TransactionAware ta = (TransactionAware) myObj;
 352  
             ta.afterAbort();
 353  
         }
 354  
     }
 355  
 
 356  
     /**
 357  
      * buildObjectImage() will return the image of the Object.
 358  
      */
 359  
     private Map buildObjectImage(PersistenceBroker broker) throws PersistenceBrokerException
 360  
     {
 361  
         Map imageMap = new HashMap();
 362  
         ClassDescriptor cld = broker.getClassDescriptor(getObject().getClass());
 363  
         //System.out.println("++++ build image: " + getObject());
 364  
         // register 1:1 references in image
 365  
         buildImageForSingleReferences(imageMap, cld);
 366  
         // put object values to image map
 367  
         buildImageForFields(imageMap, cld);
 368  
         // register 1:n and m:n references in image
 369  
         buildImageForCollectionReferences(imageMap, cld);
 370  
         return imageMap;
 371  
     }
 372  
 
 373  
     private void buildImageForSingleReferences(Map imageMap, ClassDescriptor cld)
 374  
     {
 375  
         // register all 1:1 references
 376  
         Iterator iter = cld.getObjectReferenceDescriptors(true).iterator();
 377  
         ObjectReferenceDescriptor rds;
 378  
         while(iter.hasNext())
 379  
         {
 380  
             rds = (ObjectReferenceDescriptor) iter.next();
 381  
             /*
 382  
             arminw:
 383  
             if a "super-reference" is matched (a 1:1 reference used to represent a super class)
 384  
             we don't handle it, because this will be done by the PB-api and will never be change
 385  
             */
 386  
             if(!rds.isSuperReferenceDescriptor())
 387  
             {
 388  
                 Object referenceObject = rds.getPersistentField().get(myObj);
 389  
 
 390  
                 IndirectionHandler handler = ProxyHelper.getIndirectionHandler(referenceObject);
 391  
                 /*
 392  
                 arminw:
 393  
                 if object was serialized and anonymous FK are used in the main object, the FK
 394  
                 values are null, we have to refresh (re-assign) these values before building field images.
 395  
                 This will not touch the main object itself, because we only reassign anonymous FK fields.
 396  
                 */
 397  
                 if(handler == null && referenceObject != null
 398  
                         && BrokerHelper.hasAnonymousKeyReference(rds.getClassDescriptor(), rds))
 399  
                 {
 400  
                     getBroker().serviceBrokerHelper().link(myObj, rds, false);
 401  
                 }
 402  
                 Image.SingleRef singleRef = new Image.SingleRef(this, rds, referenceObject);
 403  
                 imageMap.put(rds, singleRef);
 404  
             }
 405  
         }
 406  
     }
 407  
 
 408  
     private void buildImageForFields(Map imageMap, ClassDescriptor cld)
 409  
     {
 410  
         // register all non reference fields of object (with inherited fields)
 411  
         FieldDescriptor[] fieldDescs = cld.getFieldDescriptor(true);
 412  
         for(int i = 0; i < fieldDescs.length; i++)
 413  
         {
 414  
             addFieldImage(imageMap, fieldDescs[i]);
 415  
         }
 416  
     }
 417  
 
 418  
     private void addFieldImage(Map imageMap, FieldDescriptor fld)
 419  
     {
 420  
         // register copies of all field values
 421  
         Object value = fld.getPersistentField().get(myObj);
 422  
         // get the real sql type value
 423  
         value = fld.getFieldConversion().javaToSql(value);
 424  
         // make copy of the sql type value
 425  
         value = fld.getJdbcType().getFieldType().copy(value);
 426  
         // buffer in image the field name and the sql type value
 427  
         // wrapped by a helper class
 428  
         imageMap.put(fld.getPersistentField().getName(), new Image.Field(fld.getJdbcType().getFieldType(), value));
 429  
     }
 430  
 
 431  
     private void buildImageForCollectionReferences(Map imageMap, ClassDescriptor cld)
 432  
     {
 433  
         // register the 1:n and m:n references
 434  
         Iterator collections = cld.getCollectionDescriptors(true).iterator();
 435  
         CollectionDescriptor cds;
 436  
         while(collections.hasNext())
 437  
         {
 438  
             cds = (CollectionDescriptor) collections.next();
 439  
             Object collectionOrArray = cds.getPersistentField().get(myObj);
 440  
             Image.MultipleRef colRef = new Image.MultipleRef(this, cds, collectionOrArray);
 441  
             imageMap.put(cds, colRef);
 442  
         }
 443  
     }
 444  
 
 445  
     /**
 446  
      * Returns the Modification-state.
 447  
      * @return org.apache.ojb.server.states.ModificationState
 448  
      */
 449  
     public ModificationState getModificationState()
 450  
     {
 451  
         return modificationState;
 452  
     }
 453  
 
 454  
     /**
 455  
      * Returns true if the underlying Object needs an INSERT statement, else returns false.
 456  
      */
 457  
     public boolean needsInsert()
 458  
     {
 459  
         return this.getModificationState().needsInsert();
 460  
     }
 461  
 
 462  
     /**
 463  
      * Returns true if the underlying Object needs an UPDATE statement, else returns false.
 464  
      */
 465  
     public boolean needsUpdate()
 466  
     {
 467  
         return this.getModificationState().needsUpdate();
 468  
     }
 469  
 
 470  
     /**
 471  
      * Returns true if the underlying Object needs an UPDATE statement, else returns false.
 472  
      */
 473  
     public boolean needsDelete()
 474  
     {
 475  
         return this.getModificationState().needsDelete();
 476  
     }
 477  
 
 478  
     /**
 479  
      * Sets the initial MoificationState of the wrapped object myObj. The initial state will be StateNewDirty if myObj
 480  
      * is not persisten already. The state will be set to StateOldClean if the object is already persistent.
 481  
      */
 482  
     private void prepareInitialState(boolean isNewObject)
 483  
     {
 484  
         // determine appropriate modification state
 485  
         ModificationState initialState;
 486  
         if(isNewObject)
 487  
         {
 488  
             // if object is not already persistent it must be marked as new
 489  
             // it must be marked as dirty because it must be stored even if it will not modified during tx
 490  
             initialState = StateNewDirty.getInstance();
 491  
         }
 492  
         else if(isDeleted(oid))
 493  
         {
 494  
             // if object is already persistent it will be marked as old.
 495  
             // it is marked as dirty as it has been deleted during tx and now it is inserted again,
 496  
             // possibly with new field values.
 497  
             initialState = StateOldDirty.getInstance();
 498  
         }
 499  
         else
 500  
         {
 501  
             // if object is already persistent it will be marked as old.
 502  
             // it is marked as clean as it has not been modified during tx already
 503  
             initialState = StateOldClean.getInstance();
 504  
         }
 505  
         // remember it:
 506  
         modificationState = initialState;
 507  
     }
 508  
 
 509  
     /**
 510  
      * Checks if the object with the given identity has been deleted
 511  
      * within the transaction.
 512  
      * @param id The identity
 513  
      * @return true if the object has been deleted
 514  
      * @throws PersistenceBrokerException
 515  
      */
 516  
     public boolean isDeleted(Identity id)
 517  
     {
 518  
         ObjectEnvelope envelope = buffer.getByIdentity(id);
 519  
 
 520  
         return (envelope != null && envelope.needsDelete());
 521  
     }
 522  
 
 523  
     /**
 524  
      * set the Modification state to a new value. Used during state transitions.
 525  
      * @param newModificationState org.apache.ojb.server.states.ModificationState
 526  
      */
 527  
     public void setModificationState(ModificationState newModificationState)
 528  
     {
 529  
         if(newModificationState != modificationState)
 530  
         {
 531  
             if(log.isDebugEnabled())
 532  
             {
 533  
                 log.debug("object state transition for object " + this.oid + " ("
 534  
                         + modificationState + " --> " + newModificationState + ")");
 535  
 //                try{throw new Exception();}catch(Exception e)
 536  
 //                {
 537  
 //                e.printStackTrace();
 538  
 //                }
 539  
             }
 540  
             modificationState = newModificationState;
 541  
         }
 542  
     }
 543  
 
 544  
     /**
 545  
      * returns a String representation.
 546  
      * @return java.lang.String
 547  
      */
 548  
     public String toString()
 549  
     {
 550  
         ToStringBuilder buf = new ToStringBuilder(this);
 551  
         buf.append("Identity", oid)
 552  
             .append("ModificationState", modificationState.toString());
 553  
         return buf.toString();
 554  
     }
 555  
 
 556  
     /**
 557  
      * For internal use only! Only call immediately before commit to guarantee
 558  
      * that all changes can be detected (because this method cache the detected "change state"
 559  
      * thus on eager call changes could be ignored). Checks whether object and internal clone
 560  
      * differ and returns <em>true</em> if so, returns <em>false</em> else.
 561  
      *
 562  
      * @return boolean The result.
 563  
      */
 564  
     public boolean hasChanged(PersistenceBroker broker)
 565  
     {
 566  
         if(hasChanged == null)
 567  
         {
 568  
             Map current = null;
 569  
             try
 570  
             {
 571  
                 current = getCurrentImage();
 572  
             }
 573  
             catch(Exception e)
 574  
             {
 575  
                 log.warn("Could not verify object changes, mark dirty: " + getIdentity(), e);
 576  
             }
 577  
             if(beforeImage != null && current != null)
 578  
             {
 579  
                 Iterator it = beforeImage.entrySet().iterator();
 580  
                 hasChanged = Boolean.FALSE;
 581  
                 while(it.hasNext())
 582  
                 {
 583  
                     Map.Entry entry =  (Map.Entry) it.next();
 584  
                     Image imageBefore = (Image) entry.getValue();
 585  
                     Image imageCurrent = (Image) current.get(entry.getKey());
 586  
                     if(imageBefore.modified(imageCurrent))
 587  
                     {
 588  
                         hasChanged = Boolean.TRUE;
 589  
                         break;
 590  
                     }
 591  
                 }
 592  
             }
 593  
             else
 594  
             {
 595  
                 hasChanged = Boolean.TRUE;
 596  
             }
 597  
             if(log.isDebugEnabled())
 598  
             {
 599  
                 log.debug("State detection for " + getIdentity() + " --> object "
 600  
                         + (hasChanged.booleanValue() ? "has changed" : "unchanged"));
 601  
             }
 602  
         }
 603  
         return hasChanged.booleanValue();
 604  
     }
 605  
 
 606  
     /**
 607  
      * Mark new or deleted reference elements
 608  
      * @param broker
 609  
      */
 610  
     void markReferenceElements(PersistenceBroker broker)
 611  
     {
 612  
         // these cases will be handled by ObjectEnvelopeTable#cascadingDependents()
 613  
         // if(getModificationState().needsInsert() || getModificationState().needsDelete()) return;
 614  
 
 615  
         Map oldImage = getBeforeImage();
 616  
         Map newImage = getCurrentImage();
 617  
 
 618  
         Iterator iter = newImage.entrySet().iterator();
 619  
         while (iter.hasNext())
 620  
         {
 621  
             Map.Entry entry = (Map.Entry) iter.next();
 622  
             Object key = entry.getKey();
 623  
             // we only interested in references
 624  
             if(key instanceof ObjectReferenceDescriptor)
 625  
             {
 626  
                 Image oldRefImage = (Image) oldImage.get(key);
 627  
                 Image newRefImage = (Image) entry.getValue();
 628  
                 newRefImage.performReferenceDetection(oldRefImage);
 629  
             }
 630  
         }
 631  
     }
 632  
 
 633  
     public void doUpdate()
 634  
     {
 635  
         if(log.isDebugEnabled()) log.debug("Start UPDATE action for " + getIdentity());
 636  
         performLinkEntries();
 637  
         getBroker().store(getObject(), getIdentity(), getClassDescriptor(), false, true);
 638  
     }
 639  
 
 640  
     public void doInsert()
 641  
     {
 642  
         if(log.isDebugEnabled()) log.debug("Start INSERT action for " + getIdentity());
 643  
         performLinkEntries();
 644  
         getBroker().store(getObject(), getIdentity(), getClassDescriptor(), true, true);
 645  
         Identity oldOid = refreshIdentity();
 646  
         buffer.replaceRegisteredIdentity(getIdentity(), oldOid);
 647  
     }
 648  
 
 649  
     public void doDelete()
 650  
     {
 651  
         if(log.isDebugEnabled()) log.debug("Start DELETE action for " + getIdentity());
 652  
         getBroker().delete(getObject(), true);
 653  
     }
 654  
 
 655  
     public void doEvictFromCache()
 656  
     {
 657  
         if(log.isDebugEnabled()) log.debug("Remove from cache " + getIdentity());
 658  
         getBroker().removeFromCache(getIdentity());
 659  
     }
 660  
 
 661  
     public boolean isWriteLocked()
 662  
     {
 663  
         return writeLocked;
 664  
     }
 665  
 
 666  
     public void setWriteLocked(boolean writeLocked)
 667  
     {
 668  
         this.writeLocked = writeLocked;
 669  
     }
 670  
 
 671  
     ClassDescriptor getClassDescriptor()
 672  
     {
 673  
         return getBroker().getClassDescriptor(ProxyHelper.getRealClass(getObject()));
 674  
     }
 675  
 
 676  
     void addLinkOneToOne(ObjectReferenceDescriptor ord, boolean unlink)
 677  
     {
 678  
         LinkEntry entry = new LinkEntryOneToOne(ord, getObject(), unlink);
 679  
         linkEntryList.add(entry);
 680  
     }
 681  
 
 682  
     void addLinkOneToN(CollectionDescriptor col, Object source, boolean unlink)
 683  
     {
 684  
         if(col.isMtoNRelation()) throw new OJBRuntimeException("Expected an 1:n relation, but specified a m:n");
 685  
         LinkEntry entry = new LinkEntryOneToN(source, col, getObject(), unlink);
 686  
         linkEntryList.add(entry);
 687  
     }
 688  
 
 689  
     private void performLinkEntries()
 690  
     {
 691  
         PersistenceBroker broker = getBroker();
 692  
         for(int i = 0; i < linkEntryList.size(); i++)
 693  
         {
 694  
             LinkEntry linkEntry = (LinkEntry) linkEntryList.get(i);
 695  
             linkEntry.execute(broker);
 696  
         }
 697  
     }
 698  
 
 699  
     public void addedOneToOne(ObjectReferenceDescriptor ord, Object refObjOrProxy, Identity oid)
 700  
     {
 701  
         // the main objects needs link/unlink of the FK to 1:1 reference,
 702  
         // so mark this dirty
 703  
         setModificationState(getModificationState().markDirty());
 704  
         // if the object is already registered, OJB knows about
 705  
         // else lock and register object, get read lock, because we
 706  
         // don't know if the object is new or moved from an existing other object
 707  
         ObjectEnvelope oe = buffer.getByIdentity(oid);
 708  
         if(oe == null)
 709  
         {
 710  
             RuntimeObject rt = new RuntimeObject(refObjOrProxy, getTx());
 711  
             // we don't use cascade locking, because new reference object
 712  
             // will be detected by ObjectEnvelopeTable#cascadeMarkedForInsert()
 713  
             getTx().lockAndRegister(rt, TransactionExt.READ, false, getTx().getRegistrationList());
 714  
         }
 715  
         // in any case we need to link the main object
 716  
         addLinkOneToOne(ord, false);
 717  
     }
 718  
 
 719  
     public void deletedOneToOne(ObjectReferenceDescriptor ord, Object refObjOrProxy, Identity oid, boolean needsUnlink)
 720  
     {
 721  
         // the main objects needs link/unlink of the FK to 1:1 reference,
 722  
         // so mark this dirty
 723  
         setModificationState(getModificationState().markDirty());
 724  
         ObjectEnvelope oldRefMod = buffer.getByIdentity(oid);
 725  
         // only delete when the reference wasn't assigned with another object
 726  
         if(!buffer.isNewAssociatedObject(oid))
 727  
         {
 728  
             // if cascading delete is enabled, remove the 1:1 reference
 729  
             // because it was removed from the main object
 730  
             if(buffer.getTransaction().cascadeDeleteFor(ord))
 731  
             {
 732  
                 oldRefMod.setModificationState(oldRefMod.getModificationState().markDelete());
 733  
             }
 734  
             // unlink the main object
 735  
             if(needsUnlink) addLinkOneToOne(ord, true);
 736  
         }
 737  
     }
 738  
 
 739  
     public void addedXToN(CollectionDescriptor cod, Object refObjOrProxy, Identity oid)
 740  
     {
 741  
         ObjectEnvelope mod = buffer.getByIdentity(oid);
 742  
         // if the object isn't registered already, it can be 'new' or already 'persistent'
 743  
         if(mod == null)
 744  
         {
 745  
             boolean isNew = getTx().isTransient(null, refObjOrProxy, oid);
 746  
             mod = buffer.get(oid, refObjOrProxy, isNew);
 747  
         }
 748  
         // if the object was deleted in an previous action, mark as new
 749  
         // to avoid deletion, else mark object as dirty to assign the FK of
 750  
         // the main object
 751  
         if(mod.needsDelete())
 752  
         {
 753  
             mod.setModificationState(mod.getModificationState().markNew());
 754  
         }
 755  
         else
 756  
         {
 757  
             /*
 758  
             arminw: if the reference is a m:n relation and the object state is
 759  
             old clean, no need to update the reference.
 760  
             */
 761  
             if(!(cod.isMtoNRelation() && mod.getModificationState().equals(StateOldClean.getInstance())))
 762  
             {
 763  
                 mod.setModificationState(mod.getModificationState().markDirty());
 764  
             }
 765  
         }
 766  
         // buffer this object as "new" in a list to prevent deletion
 767  
         // when object was moved from one collection to another
 768  
         buffer.addNewAssociatedIdentity(oid);
 769  
         // new referenced object found, so register all m:n relation for "linking"
 770  
         if(cod.isMtoNRelation())
 771  
         {
 772  
             buffer.addM2NLinkEntry(cod, getObject(), refObjOrProxy);
 773  
         }
 774  
         else
 775  
         {
 776  
             // we have to link the new object
 777  
             mod.addLinkOneToN(cod, getObject(), false);
 778  
         }
 779  
         if(mod.needsInsert())
 780  
         {
 781  
             buffer.addForInsertDependent(mod);
 782  
         }
 783  
     }
 784  
 
 785  
     public void deletedXToN(CollectionDescriptor cod, Object refObjOrProxy, Identity oid)
 786  
     {
 787  
         ObjectEnvelope mod = buffer.getByIdentity(oid);
 788  
         // if this object is associated with another object it's
 789  
         // not allowed to remove it, thus nothing will change
 790  
         if(!buffer.isNewAssociatedObject(oid))
 791  
         {
 792  
             if(mod != null)
 793  
             {
 794  
                 boolean cascade = buffer.getTransaction().cascadeDeleteFor(cod);
 795  
                 if(cascade)
 796  
                 {
 797  
                     mod.setModificationState(mod.getModificationState().markDelete());
 798  
                     buffer.addForDeletionDependent(mod);
 799  
                 }
 800  
                 if(cod.isMtoNRelation())
 801  
                 {
 802  
                     buffer.addM2NUnlinkEntry(cod, getObject(), refObjOrProxy);
 803  
                 }
 804  
                 else
 805  
                 {
 806  
                     // when cascade 'true' we remove all dependent objects, so no need
 807  
                     // to unlink, else we have to unlink all referenced objects of this
 808  
                     // object
 809  
                     if(!cascade)
 810  
                     {
 811  
                         mod.setModificationState(mod.getModificationState().markDirty());
 812  
                         mod.addLinkOneToN(cod, getObject(), true);
 813  
                     }
 814  
                 }
 815  
             }
 816  
             else
 817  
             {
 818  
                 throw new Image.ImageException("Unexpected behaviour, unregistered object to delete: "
 819  
                         + oid + ", main object is " + getIdentity()+ ", envelope object is " + this.toString());
 820  
             }
 821  
         }
 822  
     }
 823  
 }