Coverage Report - org.apache.ojb.odmg.ObjectEnvelopeOrdering
 
Classes in this File Line Coverage Branch Coverage Complexity
ObjectEnvelopeOrdering
N/A
N/A
3.935
ObjectEnvelopeOrdering$Edge
N/A
N/A
3.935
ObjectEnvelopeOrdering$Vertex
N/A
N/A
3.935
 
 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  
 import java.util.ArrayList;
 19  
 import java.util.Iterator;
 20  
 import java.util.List;
 21  
 import java.util.Map;
 22  
 
 23  
 import org.apache.commons.lang.ArrayUtils;
 24  
 import org.apache.commons.lang.builder.ToStringBuilder;
 25  
 import org.apache.ojb.broker.Identity;
 26  
 import org.apache.ojb.broker.core.proxy.ProxyHelper;
 27  
 import org.apache.ojb.broker.metadata.ClassDescriptor;
 28  
 import org.apache.ojb.broker.metadata.CollectionDescriptor;
 29  
 import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
 30  
 import org.apache.ojb.broker.util.BrokerHelper;
 31  
 import org.apache.ojb.broker.util.logging.Logger;
 32  
 import org.apache.ojb.broker.util.logging.LoggerFactory;
 33  
 import org.apache.ojb.odmg.states.ModificationState;
 34  
 
 35  
 /**
 36  
  * <p>Implements an algorithm for reordering the object envelopes of a pending
 37  
  * transaction to minimized the probability of foreign key constraint
 38  
  * violations.</p>
 39  
  * 
 40  
  * <p>The algorithm is based on a graph theoretical approach: Each object
 41  
  * envelope is represented by a vertex in a graph. Each possible constraint
 42  
  * on the order of database operations is represented by a directed edge
 43  
  * in this graph, in which the initial vertex represents the object envelope
 44  
  * to be sent to the database first and the terminal index represents the
 45  
  * object envelope that might cause a FK violation if the initial vertex
 46  
  * has not been sent to the database before.</p> 
 47  
  * 
 48  
  * <p>Additionally the edges in this graph are weighted. This is necessary
 49  
  * because the object envelopes provide only information on the relation
 50  
  * between objects <strong>after</strong> the transaction. FK violations, 
 51  
  * however, can also occur due to relations that existed <strong>before</strong>
 52  
  * the transaction. Therefore the algorithm also considers potential relations
 53  
  * between objects due to the fact that an object is of a class that is
 54  
  * the item class of a object or collection reference of another object.
 55  
  * Graph edges representing such potential relationships receive a lower
 56  
  * weight than edges representing concrete relationships that exist in the
 57  
  * current state of the object model.</p>
 58  
  * 
 59  
  * <p>Once all graph edges have been established, the algorithm proceeds as
 60  
  * follows:</p>
 61  
  * <ol>
 62  
  *  <li>Iterate through all vertices and sum up the weight of all incoming
 63  
  *      edges (i.e. those edges whose terminal vertex is the current vertex).</li>
 64  
  *  <li>Find the minimum value of this weight sums (ideally this minimum is zero,
 65  
  *      meaning that there are object envelopes that can be sent to the 
 66  
  *      database without risking FK violations)</li>
 67  
  *  <li>Add all vertices with a weight sum that is equal to this minimum to the
 68  
  *      reordered sequence of object envelopes and remove the vertices
 69  
  *      and all connected edges from the graph.</li>
 70  
  *  <li>If there are vertices left, repeat steps (1) through (3), otherwise
 71  
  *      we are done.
 72  
  * </ol>
 73  
  *
 74  
  * @author  Gerhard Grosse
 75  
  * @version $Id: ObjectEnvelopeOrdering.java,v 1.1 2007-08-24 22:17:37 ewestfal Exp $
 76  
  * @since   Nov 15, 2004
 77  
  */
 78  
 class ObjectEnvelopeOrdering
 79  
 {
 80  
     private static final int CONCRETE_EDGE_WEIGHT = 3;
 81  
     private static final int CONCRETE_EDGE_WEIGHT_WITH_FK = 4;
 82  
     private static final int POTENTIAL_EDGE_WEIGHT = 1;
 83  
     private static final int POTENTIAL_EDGE_WEIGHT_WITH_FK = 2;
 84  
     private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
 85  
 
 86  
     private static Logger log = LoggerFactory.getLogger(ObjectEnvelopeOrdering.class);
 87  
 
 88  
     private List originalOrder;
 89  
     private Map envelopes;
 90  
 
 91  
     private Vertex[] vertices;
 92  
     private List edgeList;
 93  
 
 94  
     private Identity[] newOrder;
 95  
 
 96  
     /**
 97  
      * Creates an object envelope ordering based on an original ordering
 98  
      * of Identity objects and an Identity-&gt;ObjectEnvelope map
 99  
      * @param originalOrder a list of Identity objects
 100  
      * @param envelopes a map with ObjectEnvelope-s with their respective
 101  
      *      Identity-s as key
 102  
      */
 103  
     public ObjectEnvelopeOrdering(List originalOrder, Map envelopes)
 104  
     {
 105  
         this.originalOrder = originalOrder;
 106  
         this.envelopes = envelopes;
 107  
     }
 108  
 
 109  
     /**
 110  
      * Reorders the object envelopes. The new order is available from the
 111  
      * <code>ordering</code> property.
 112  
      * @see #getOrdering()
 113  
      */
 114  
     public void reorder()
 115  
     {
 116  
         int newOrderIndex = 0;
 117  
         long t1 = 0, t2 = 0, t3;
 118  
 
 119  
         if (log.isDebugEnabled())
 120  
         {
 121  
             t1 = System.currentTimeMillis();
 122  
         }
 123  
         newOrder = new Identity[originalOrder.size()];
 124  
 
 125  
         if(log.isDebugEnabled()) log.debug("Orginal order: " + originalOrder);
 126  
         // set up the vertex array in the order the envelopes were added
 127  
         List vertexList = new ArrayList(originalOrder.size());
 128  
         // int vertexIndex = 0;
 129  
         for (Iterator it = originalOrder.iterator(); it.hasNext();)
 130  
         {
 131  
             ObjectEnvelope envelope = (ObjectEnvelope) envelopes.get(it.next());
 132  
             if (envelope.needsUpdate() || envelope.needsInsert() || envelope.needsDelete())
 133  
             {
 134  
                 Vertex vertex = new Vertex(envelope);
 135  
                 vertexList.add(vertex);
 136  
                 if (log.isDebugEnabled())
 137  
                 {
 138  
                     log.debug("Add new Vertex object "+envelope.getIdentity()+" to VertexList");
 139  
                 }
 140  
             }
 141  
             else
 142  
             {
 143  
                 // envelope is clean - just add identity to new order
 144  
                 newOrder[newOrderIndex++] = envelope.getIdentity();
 145  
                 if (log.isDebugEnabled())
 146  
                 {
 147  
                     log.debug("Add unmodified object "+envelope.getIdentity()+" to new OrderList");
 148  
                 }
 149  
             }
 150  
         }
 151  
         vertices = (Vertex[]) vertexList.toArray(new Vertex[vertexList.size()]);
 152  
 
 153  
         // set up the edges
 154  
         edgeList = new ArrayList(2 * vertices.length);
 155  
         for (int i = 0; i < vertices.length; i++)
 156  
         {
 157  
             addEdgesForVertex(vertices[i]);
 158  
         }
 159  
 
 160  
         if (log.isDebugEnabled())
 161  
         {
 162  
             t2 = System.currentTimeMillis();
 163  
             log.debug("Building object envelope graph took " + (t2 - t1) + " ms");
 164  
             log.debug("Object envelope graph contains " + vertices.length + " vertices" + " and " + edgeList.size()
 165  
                     + " edges");
 166  
         }
 167  
 
 168  
         int remainingVertices = vertices.length;
 169  
         int iterationCount = 0;
 170  
         while (remainingVertices > 0)
 171  
         {
 172  
             // update iteration count
 173  
             iterationCount++;
 174  
 
 175  
             // update incoming edge counts
 176  
             for (Iterator it = edgeList.iterator(); it.hasNext();)
 177  
             {
 178  
                 Edge edge = (Edge) it.next();
 179  
                 if (!edge.isProcessed())
 180  
                 {
 181  
                     if(log.isDebugEnabled())
 182  
                     {
 183  
                         final String msg = "Add weight '"+edge.getWeight()+"' for terminal vertex " + edge.getTerminalVertex() + " of edge " + edge;
 184  
                         log.debug(msg);
 185  
                     }
 186  
                     edge.getTerminalVertex().incrementIncomingEdgeWeight(edge.getWeight());
 187  
                 }
 188  
             }
 189  
 
 190  
             // find minimum weight of incoming edges of a vertex
 191  
             int minIncomingEdgeWeight = Integer.MAX_VALUE;
 192  
             for (int i = 0; i < vertices.length; i++)
 193  
             {
 194  
                 Vertex vertex = vertices[i];
 195  
                 if (!vertex.isProcessed() && minIncomingEdgeWeight > vertex.getIncomingEdgeWeight())
 196  
                 {
 197  
                     minIncomingEdgeWeight = vertex.getIncomingEdgeWeight();
 198  
                     if (minIncomingEdgeWeight == 0)
 199  
                     {
 200  
                         // we won't get any lower
 201  
                         break;
 202  
                     }
 203  
                 }
 204  
             }
 205  
 
 206  
             // process vertices having minimum incoming edge weight
 207  
             int processCount = 0;
 208  
             for (int i = 0; i < vertices.length; i++)
 209  
             {
 210  
                 Vertex vertex = vertices[i];
 211  
                 if (!vertex.isProcessed() && vertex.getIncomingEdgeWeight() == minIncomingEdgeWeight)
 212  
                 {
 213  
                     newOrder[newOrderIndex++] = vertex.getEnvelope().getIdentity();
 214  
                     vertex.markProcessed();
 215  
                     processCount++;
 216  
                     if (log.isDebugEnabled())
 217  
                     {
 218  
                         log.debug("add minimum edge weight - "+minIncomingEdgeWeight
 219  
                                 + ", newOrderList: " + ArrayUtils.toString(newOrder));
 220  
                     }
 221  
                 }
 222  
                 vertex.resetIncomingEdgeWeight();
 223  
             }
 224  
 
 225  
             if (log.isDebugEnabled())
 226  
             {
 227  
                 log.debug("Processed " + processCount + " of " + remainingVertices
 228  
                         + " remaining vertices in iteration #" + iterationCount);
 229  
             }
 230  
             remainingVertices -= processCount;
 231  
         }
 232  
 
 233  
         if (log.isDebugEnabled())
 234  
         {
 235  
             t3 = System.currentTimeMillis();
 236  
             log.debug("New ordering: " + ArrayUtils.toString(newOrder));
 237  
             log.debug("Processing object envelope graph took " + (t3 - t2) + " ms");
 238  
         }
 239  
 
 240  
     }
 241  
 
 242  
     /**
 243  
      * Gets the reordered sequence of object envelopes
 244  
      * @return an array of Identity objects representing the opimized sequence
 245  
      *      of database operations
 246  
      */
 247  
     public Identity[] getOrdering()
 248  
     {
 249  
         if (newOrder == null)
 250  
         {
 251  
             reorder();
 252  
         }
 253  
         return newOrder;
 254  
     }
 255  
 
 256  
     /**
 257  
      * Adds all edges for a given object envelope vertex. All edges are
 258  
      * added to the edgeList map.
 259  
      * @param vertex the Vertex object to find edges for
 260  
      */
 261  
     private void addEdgesForVertex(Vertex vertex)
 262  
     {
 263  
         ClassDescriptor cld = vertex.getEnvelope().getClassDescriptor();
 264  
         Iterator rdsIter = cld.getObjectReferenceDescriptors(true).iterator();
 265  
         while (rdsIter.hasNext())
 266  
         {
 267  
             ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) rdsIter.next();
 268  
             addObjectReferenceEdges(vertex, rds);
 269  
         }
 270  
         Iterator cdsIter = cld.getCollectionDescriptors(true).iterator();
 271  
         while (cdsIter.hasNext())
 272  
         {
 273  
             CollectionDescriptor cds = (CollectionDescriptor) cdsIter.next();
 274  
             addCollectionEdges(vertex, cds);
 275  
         }
 276  
     }
 277  
 
 278  
     /**
 279  
      * Finds edges based to a specific object reference descriptor and
 280  
      * adds them to the edge map.
 281  
      * @param vertex the object envelope vertex holding the object reference
 282  
      * @param rds the object reference descriptor
 283  
      */
 284  
     private void addObjectReferenceEdges(Vertex vertex, ObjectReferenceDescriptor rds)
 285  
     {
 286  
         Object refObject = rds.getPersistentField().get(vertex.getEnvelope().getRealObject());
 287  
         Class refClass = rds.getItemClass();
 288  
         for (int i = 0; i < vertices.length; i++)
 289  
         {
 290  
             Edge edge = null;
 291  
             // ObjectEnvelope envelope = vertex.getEnvelope();
 292  
             Vertex refVertex = vertices[i];
 293  
             ObjectEnvelope refEnvelope = refVertex.getEnvelope();
 294  
             if (refObject == refEnvelope.getRealObject())
 295  
             {
 296  
                 edge = buildConcrete11Edge(vertex, refVertex, rds.hasConstraint());
 297  
             }
 298  
             else if (refClass.isInstance(refVertex.getEnvelope().getRealObject()))
 299  
             {
 300  
                 edge = buildPotential11Edge(vertex, refVertex, rds.hasConstraint());
 301  
             }
 302  
             if (edge != null)
 303  
             {
 304  
                 if (!edgeList.contains(edge))
 305  
                 {
 306  
                     edgeList.add(edge);
 307  
                 }
 308  
                 else
 309  
                 {
 310  
                     edge.increaseWeightTo(edge.getWeight());
 311  
                 }
 312  
             }
 313  
         }
 314  
     }
 315  
 
 316  
     /**
 317  
      * Finds edges base on a specific collection descriptor (1:n and m:n)
 318  
      * and adds them to the edge map.
 319  
      * @param vertex the object envelope vertex holding the collection
 320  
      * @param cds the collection descriptor
 321  
      */
 322  
     private void addCollectionEdges(Vertex vertex, CollectionDescriptor cds)
 323  
     {
 324  
         ObjectEnvelope envelope = vertex.getEnvelope();
 325  
         Object col = cds.getPersistentField().get(envelope.getRealObject());
 326  
         Object[] refObjects;
 327  
         if (col == null || (ProxyHelper.isCollectionProxy(col) && !ProxyHelper.getCollectionProxy(col).isLoaded()))
 328  
         {
 329  
             refObjects = EMPTY_OBJECT_ARRAY;
 330  
         }
 331  
         else
 332  
         {
 333  
             refObjects = BrokerHelper.getCollectionArray(col);
 334  
         }
 335  
         Class refClass = cds.getItemClass();
 336  
 
 337  
         for (int i = 0; i < vertices.length; i++)
 338  
         {
 339  
             Edge edge = null;
 340  
             Vertex refVertex = vertices[i];
 341  
             ObjectEnvelope refEnvelope = refVertex.getEnvelope();
 342  
 
 343  
             if (refClass.isInstance(refEnvelope.getRealObject()))
 344  
             {
 345  
                 if (containsObject(refEnvelope.getRealObject(), refObjects))
 346  
                 {
 347  
                     if (cds.isMtoNRelation())
 348  
                     {
 349  
                         edge = buildConcreteMNEdge(vertex, refVertex);
 350  
                     }
 351  
                     else
 352  
                     {
 353  
                         edge = buildConcrete1NEdge(vertex, refVertex);
 354  
                     }
 355  
                 }
 356  
                 else
 357  
                 {
 358  
                     if (cds.isMtoNRelation())
 359  
                     {
 360  
                         edge = buildPotentialMNEdge(vertex, refVertex);
 361  
                     }
 362  
                     else
 363  
                     {
 364  
                         edge = buildPotential1NEdge(vertex, refVertex);
 365  
                     }
 366  
                 }
 367  
             }
 368  
             if (edge != null)
 369  
             {
 370  
                 if (!edgeList.contains(edge))
 371  
                 {
 372  
                     edgeList.add(edge);
 373  
                 }
 374  
                 else
 375  
                 {
 376  
                     edge.increaseWeightTo(edge.getWeight());
 377  
                 }
 378  
             }
 379  
         }
 380  
     }
 381  
 
 382  
     /**
 383  
      * Helper method that searches an object array for the occurence of a
 384  
      * specific object based on reference equality
 385  
      * @param searchFor the object to search for
 386  
      * @param searchIn the array to search in
 387  
      * @return true if the object is found, otherwise false
 388  
      */
 389  
     private static boolean containsObject(Object searchFor, Object[] searchIn)
 390  
     {
 391  
         for (int i = 0; i < searchIn.length; i++)
 392  
         {
 393  
             if (searchFor == searchIn[i])
 394  
             {
 395  
                 return true;
 396  
             }
 397  
         }
 398  
         return false;
 399  
     }
 400  
 
 401  
     /**
 402  
      * Checks if the database operations associated with two object envelopes
 403  
      * that are related via an 1:1 (or n:1) reference needs to be performed 
 404  
      * in a particular order and if so builds and returns a corresponding 
 405  
      * directed edge weighted with <code>CONCRETE_EDGE_WEIGHT</code>.
 406  
      * The following cases are considered (* means object needs update, + means
 407  
      * object needs insert, - means object needs to be deleted):
 408  
      * <table>
 409  
      *  <tr><td>(1)* -(1:1)-&gt; (2)*</td><td>no edge</td></tr>
 410  
      *  <tr><td>(1)* -(1:1)-&gt; (2)+</td><td>(2)-&gt;(1) edge</td></tr>
 411  
      *  <tr><td>(1)* -(1:1)-&gt; (2)-</td><td>no edge (cannot occur)</td></tr>
 412  
      *  <tr><td>(1)+ -(1:1)-&gt; (2)*</td><td>no edge</td></tr>
 413  
      *  <tr><td>(1)+ -(1:1)-&gt; (2)+</td><td>(2)-&gt;(1) edge</td></tr>
 414  
      *  <tr><td>(1)+ -(1:1)-&gt; (2)-</td><td>no edge (cannot occur)</td></tr>
 415  
      *  <tr><td>(1)- -(1:1)-&gt; (2)*</td><td>no edge</td></tr>
 416  
      *  <tr><td>(1)- -(1:1)-&gt; (2)+</td><td>no edge</td></tr>
 417  
      *  <tr><td>(1)- -(1:1)-&gt; (2)-</td><td>(1)-&gt;(2) edge</td></tr>
 418  
      * <table>
 419  
      * @param vertex1 object envelope vertex of the object holding the reference
 420  
      * @param vertex2 object envelope vertex of the referenced object
 421  
      * @return an Edge object or null if the two database operations can
 422  
      *      be performed in any order
 423  
      */
 424  
     protected Edge buildConcrete11Edge(Vertex vertex1, Vertex vertex2, boolean fkToRef)
 425  
     {
 426  
         ModificationState state1 = vertex1.getEnvelope().getModificationState();
 427  
         ModificationState state2 = vertex2.getEnvelope().getModificationState();
 428  
         if (state1.needsUpdate() || state1.needsInsert())
 429  
         {
 430  
             if (state2.needsInsert())
 431  
             {
 432  
                 // (2) must be inserted before (1) can point to it
 433  
                 return new Edge(vertex2, vertex1, fkToRef ? CONCRETE_EDGE_WEIGHT_WITH_FK : CONCRETE_EDGE_WEIGHT);
 434  
             }
 435  
         }
 436  
         else if (state1.needsDelete())
 437  
         {
 438  
             if (state2.needsDelete())
 439  
             {
 440  
                 // (1) points to (2) and must be deleted first 
 441  
                 return new Edge(vertex1, vertex2, fkToRef ? CONCRETE_EDGE_WEIGHT_WITH_FK : CONCRETE_EDGE_WEIGHT);
 442  
             }
 443  
         }
 444  
         return null;
 445  
     }
 446  
 
 447  
     /**
 448  
      * Checks if the database operations associated with two object envelopes
 449  
      * that might have been related via an 1:1 (or n:1) reference before
 450  
      * the current transaction needs to be performed 
 451  
      * in a particular order and if so builds and returns a corresponding 
 452  
      * directed edge weighted with <code>POTENTIAL_EDGE_WEIGHT</code>. 
 453  
      * The following cases are considered (* means object needs update, + means
 454  
      * object needs insert, - means object needs to be deleted):
 455  
      * <table>
 456  
      *  <tr><td>(1)* -(1:1)-&gt; (2)*</td><td>no edge</td></tr>
 457  
      *  <tr><td>(1)* -(1:1)-&gt; (2)+</td><td>no edge</td></tr>
 458  
      *  <tr><td>(1)* -(1:1)-&gt; (2)-</td><td>(1)-&gt;(2) edge</td></tr>
 459  
      *  <tr><td>(1)+ -(1:1)-&gt; (2)*</td><td>no edge</td></tr>
 460  
      *  <tr><td>(1)+ -(1:1)-&gt; (2)+</td><td>no edge</td></tr>
 461  
      *  <tr><td>(1)+ -(1:1)-&gt; (2)-</td><td>no edge</td></tr>
 462  
      *  <tr><td>(1)- -(1:1)-&gt; (2)*</td><td>no edge</td></tr>
 463  
      *  <tr><td>(1)- -(1:1)-&gt; (2)+</td><td>no edge</td></tr>
 464  
      *  <tr><td>(1)- -(1:1)-&gt; (2)-</td><td>(1)-&gt;(2) edge</td></tr>
 465  
      * <table>
 466  
      * @param vertex1 object envelope vertex of the object that might have 
 467  
      *      hold the reference
 468  
      * @param vertex2 object envelope vertex of the potentially referenced 
 469  
      *      object
 470  
      * @return an Edge object or null if the two database operations can
 471  
      *      be performed in any order
 472  
      */
 473  
     protected Edge buildPotential11Edge(Vertex vertex1, Vertex vertex2, boolean fkToRef)
 474  
     {
 475  
         ModificationState state1 = vertex1.getEnvelope().getModificationState();
 476  
         ModificationState state2 = vertex2.getEnvelope().getModificationState();
 477  
         if (state1.needsUpdate() || state1.needsDelete())
 478  
         {
 479  
             if (state2.needsDelete())
 480  
             {
 481  
                 // old version of (1) might point to (2)
 482  
                 return new Edge(vertex1, vertex2, fkToRef ? POTENTIAL_EDGE_WEIGHT_WITH_FK : POTENTIAL_EDGE_WEIGHT);
 483  
             }
 484  
         }
 485  
         return null;
 486  
     }
 487  
 
 488  
     /**
 489  
      * Checks if the database operations associated with two object envelopes
 490  
      * that are related via an 1:n collection reference needs to be performed 
 491  
      * in a particular order and if so builds and returns a corresponding 
 492  
      * directed edge weighted with <code>CONCRETE_EDGE_WEIGHT</code>.
 493  
      * The following cases are considered (* means object needs update, + means
 494  
      * object needs insert, - means object needs to be deleted):
 495  
      * <table>
 496  
      *  <tr><td>(1)* -(1:n)-&gt; (2)*</td><td>no edge</td></tr>
 497  
      *  <tr><td>(1)* -(1:n)-&gt; (2)+</td><td>no edge</td></tr>
 498  
      *  <tr><td>(1)* -(1:n)-&gt; (2)-</td><td>no edge</td></tr>
 499  
      *  <tr><td>(1)+ -(1:n)-&gt; (2)*</td><td>(1)-&gt;(2) edge</td></tr>
 500  
      *  <tr><td>(1)+ -(1:n)-&gt; (2)+</td><td>(1)-&gt;(2) edge</td></tr>
 501  
      *  <tr><td>(1)+ -(1:n)-&gt; (2)-</td><td>no edge (cannot occur)</td></tr>
 502  
      *  <tr><td>(1)- -(1:n)-&gt; (2)*</td><td>(2)-&gt;(1) edge</td></tr>
 503  
      *  <tr><td>(1)- -(1:n)-&gt; (2)+</td><td>no edge</td></tr>
 504  
      *  <tr><td>(1)- -(1:n)-&gt; (2)-</td><td>(2)-&gt;(1) edge</td></tr>
 505  
      * <table>
 506  
      * @param vertex1 object envelope vertex of the object holding the 
 507  
      *      collection
 508  
      * @param vertex2 object envelope vertex of the object contained in the 
 509  
      *      collection
 510  
      * @return an Edge object or null if the two database operations can
 511  
      *      be performed in any order
 512  
      */
 513  
     protected Edge buildConcrete1NEdge(Vertex vertex1, Vertex vertex2)
 514  
     {
 515  
         ModificationState state1 = vertex1.getEnvelope().getModificationState();
 516  
         ModificationState state2 = vertex2.getEnvelope().getModificationState();
 517  
         if (state1.needsInsert())
 518  
         {
 519  
             if (state2.needsUpdate() || state2.needsInsert())
 520  
             {
 521  
                 // (2) now contains an FK to (1) thus (1) must be inserted first
 522  
                 return new Edge(vertex1, vertex2, CONCRETE_EDGE_WEIGHT);
 523  
             }
 524  
         }
 525  
         else if (state1.needsDelete())
 526  
         {
 527  
             if (state2.needsUpdate() || state2.needsDelete())
 528  
             {
 529  
                 // Before deleting (1) give (2) a chance to drop its FK to it 
 530  
                 return new Edge(vertex2, vertex1, CONCRETE_EDGE_WEIGHT);
 531  
             }
 532  
         }
 533  
         return null;
 534  
     }
 535  
 
 536  
     /**
 537  
      * Checks if the database operations associated with two object envelopes
 538  
      * that are might have been related via an 1:n collection reference before
 539  
      * the current transaction needs to be performed 
 540  
      * in a particular order and if so builds and returns a corresponding 
 541  
      * directed edge weighted with <code>POTENTIAL_EDGE_WEIGHT</code>.
 542  
      * The following cases are considered (* means object needs update, + means
 543  
      * object needs insert, - means object needs to be deleted):
 544  
      * <table>
 545  
      *  <tr><td>(1)* -(1:n)-&gt; (2)*</td><td>no edge</td></tr>
 546  
      *  <tr><td>(1)* -(1:n)-&gt; (2)+</td><td>no edge</td></tr>
 547  
      *  <tr><td>(1)* -(1:n)-&gt; (2)-</td><td>no edge</td></tr>
 548  
      *  <tr><td>(1)+ -(1:n)-&gt; (2)*</td><td>no edge</td></tr>
 549  
      *  <tr><td>(1)+ -(1:n)-&gt; (2)+</td><td>no edge</td></tr>
 550  
      *  <tr><td>(1)+ -(1:n)-&gt; (2)-</td><td>no edge</td></tr>
 551  
      *  <tr><td>(1)- -(1:n)-&gt; (2)*</td><td>(2)-&gt;(1) edge</td></tr>
 552  
      *  <tr><td>(1)- -(1:n)-&gt; (2)+</td><td>no edge</td></tr>
 553  
      *  <tr><td>(1)- -(1:n)-&gt; (2)-</td><td>(2)-&gt;(1) edge</td></tr>
 554  
      * <table>
 555  
      * @param vertex1 object envelope vertex of the object holding the 
 556  
      *      collection
 557  
      * @param vertex2 object envelope vertex of the object that might have
 558  
      *      been contained in the collection
 559  
      * @return an Edge object or null if the two database operations can
 560  
      *      be performed in any order
 561  
      */
 562  
     protected Edge buildPotential1NEdge(Vertex vertex1, Vertex vertex2)
 563  
     {
 564  
         ModificationState state1 = vertex1.getEnvelope().getModificationState();
 565  
         ModificationState state2 = vertex2.getEnvelope().getModificationState();
 566  
         if (state1.needsDelete())
 567  
         {
 568  
             if (state2.needsUpdate() || state2.needsDelete())
 569  
             {
 570  
                 // Before deleting (1) give potential previous collection 
 571  
                 // members a chance to drop their FKs to it 
 572  
                 return new Edge(vertex2, vertex1, POTENTIAL_EDGE_WEIGHT);
 573  
             }
 574  
         }
 575  
         return null;
 576  
     }
 577  
 
 578  
     /**
 579  
      * Checks if the database operations associated with two object envelopes
 580  
      * that are related via an m:n collection reference needs to be performed 
 581  
      * in a particular order and if so builds and returns a corresponding 
 582  
      * directed edge weighted with <code>CONCRETE_EDGE_WEIGHT</code>.
 583  
      * The following cases are considered (* means object needs update, + means
 584  
      * object needs insert, - means object needs to be deleted):
 585  
      * <table>
 586  
      *  <tr><td>(1)* -(m:n)-&gt; (2)*</td><td>no edge</td></tr>
 587  
      *  <tr><td>(1)* -(m:n)-&gt; (2)+</td><td>(2)-&gt;(1) edge</td></tr>
 588  
      *  <tr><td>(1)* -(m:n)-&gt; (2)-</td><td>no edge (cannot occur)</td></tr>
 589  
      *  <tr><td>(1)+ -(m:n)-&gt; (2)*</td><td>no edge</td></tr>
 590  
      *  <tr><td>(1)+ -(m:n)-&gt; (2)+</td><td>(2)-&gt;(1) edge</td></tr>
 591  
      *  <tr><td>(1)+ -(m:n)-&gt; (2)-</td><td>no edge (cannot occur)</td></tr>
 592  
      *  <tr><td>(1)- -(m:n)-&gt; (2)*</td><td>no edge</td></tr>
 593  
      *  <tr><td>(1)- -(m:n)-&gt; (2)+</td><td>no edge</td></tr>
 594  
      *  <tr><td>(1)- -(m:n)-&gt; (2)-</td><td>(1)-&gt;(2) edge</td></tr>
 595  
      * <table>
 596  
      * @param vertex1 object envelope vertex of the object holding the 
 597  
      *      collection
 598  
      * @param vertex2 object envelope vertex of the object contained in the 
 599  
      *      collection
 600  
      * @return an Edge object or null if the two database operations can
 601  
      *      be performed in any order
 602  
      */
 603  
     protected Edge buildConcreteMNEdge(Vertex vertex1, Vertex vertex2)
 604  
     {
 605  
         ModificationState state1 = vertex1.getEnvelope().getModificationState();
 606  
         ModificationState state2 = vertex2.getEnvelope().getModificationState();
 607  
         if (state1.needsUpdate() || state1.needsInsert())
 608  
         {
 609  
             if (state2.needsInsert())
 610  
             {
 611  
                 // (2) must be inserted before we can create a link to it
 612  
                 return new Edge(vertex2, vertex1, CONCRETE_EDGE_WEIGHT);
 613  
             }
 614  
         }
 615  
         else if (state1.needsDelete())
 616  
         {
 617  
             if (state2.needsDelete())
 618  
             {
 619  
                 // there is a link from (1) to (2) which must be deleted first,
 620  
                 // which will happen when deleting (1) - thus:
 621  
                 return new Edge(vertex1, vertex2, POTENTIAL_EDGE_WEIGHT);
 622  
             }
 623  
         }
 624  
         return null;
 625  
     }
 626  
 
 627  
     /**
 628  
      * Checks if the database operations associated with two object envelopes
 629  
      * that might have been related via an m:n collection reference before
 630  
      * the current transaction needs to be performed 
 631  
      * in a particular order and if so builds and returns a corresponding 
 632  
      * directed edge weighted with <code>POTENTIAL_EDGE_WEIGHT</code>.
 633  
      * The following cases are considered (* means object needs update, + means
 634  
      * object needs insert, - means object needs to be deleted):
 635  
      * <table>
 636  
      *  <tr><td>(1)* -(m:n)-&gt; (2)*</td><td>no edge</td></tr>
 637  
      *  <tr><td>(1)* -(m:n)-&gt; (2)+</td><td>no edge</td></tr>
 638  
      *  <tr><td>(1)* -(m:n)-&gt; (2)-</td><td>(1)-&gt;(2) edge</td></tr>
 639  
      *  <tr><td>(1)+ -(m:n)-&gt; (2)*</td><td>no edge</td></tr>
 640  
      *  <tr><td>(1)+ -(m:n)-&gt; (2)+</td><td>no edge</td></tr>
 641  
      *  <tr><td>(1)+ -(m:n)-&gt; (2)-</td><td>no edge</td></tr>
 642  
      *  <tr><td>(1)- -(m:n)-&gt; (2)*</td><td>no edge</td></tr>
 643  
      *  <tr><td>(1)- -(m:n)-&gt; (2)+</td><td>no edge</td></tr>
 644  
      *  <tr><td>(1)- -(m:n)-&gt; (2)-</td><td>(1)-&gt;(2) edge</td></tr>
 645  
      * <table>
 646  
      * @param vertex1 object envelope vertex of the object holding the 
 647  
      *      collection
 648  
      * @param vertex2 object envelope vertex of the object that might have
 649  
      *      been contained in the collection
 650  
      * @return an Edge object or null if the two database operations can
 651  
      *      be performed in any order
 652  
      */
 653  
     protected Edge buildPotentialMNEdge(Vertex vertex1, Vertex vertex2)
 654  
     {
 655  
         ModificationState state1 = vertex1.getEnvelope().getModificationState();
 656  
         ModificationState state2 = vertex2.getEnvelope().getModificationState();
 657  
         if (state1.needsUpdate() || state1.needsDelete())
 658  
         {
 659  
             if (state2.needsDelete())
 660  
             {
 661  
                 // old version of (1) might comprise a link to (2)
 662  
                 return new Edge(vertex1, vertex2, POTENTIAL_EDGE_WEIGHT);
 663  
             }
 664  
         }
 665  
         return null;
 666  
     }
 667  
 
 668  
     /**
 669  
      * Represents an edge in the object envelope graph
 670  
      */
 671  
     private static class Edge
 672  
     {
 673  
         private Vertex initial;
 674  
         private Vertex terminal;
 675  
         private Identity initialIdentity;
 676  
         private Identity terminalIdentity;
 677  
         private int weight;
 678  
         private boolean knownToBeProcessed;
 679  
         private int hashCode;
 680  
 
 681  
         public Edge(Vertex initial, Vertex terminal, int weight)
 682  
         {
 683  
             this.initial = initial;
 684  
             this.terminal = terminal;
 685  
             this.initialIdentity = initial.getEnvelope().getIdentity();
 686  
             this.terminalIdentity = terminal.getEnvelope().getIdentity();
 687  
             this.weight = weight;
 688  
             this.knownToBeProcessed = false;
 689  
             this.hashCode = initialIdentity.hashCode() + 13 * terminalIdentity.hashCode();
 690  
         }
 691  
 
 692  
         public Vertex getInitialVertex()
 693  
         {
 694  
             return initial;
 695  
         }
 696  
 
 697  
         public Vertex getTerminalVertex()
 698  
         {
 699  
             return terminal;
 700  
         }
 701  
 
 702  
         public boolean connects(Vertex vertex)
 703  
         {
 704  
             return initial == vertex || terminal == vertex;
 705  
         }
 706  
 
 707  
         public void increaseWeightTo(int minWeight)
 708  
         {
 709  
             if (weight < minWeight)
 710  
             {
 711  
                 weight = minWeight;
 712  
             }
 713  
         }
 714  
 
 715  
         public int getWeight()
 716  
         {
 717  
             return weight;
 718  
         }
 719  
 
 720  
         public boolean isProcessed()
 721  
         {
 722  
             if (knownToBeProcessed)
 723  
             {
 724  
                 return true;
 725  
             }
 726  
             else
 727  
             {
 728  
                 boolean processed = initial.isProcessed() || terminal.isProcessed();
 729  
                 if (processed)
 730  
                 {
 731  
                     knownToBeProcessed = true;
 732  
                 }
 733  
                 return processed;
 734  
             }
 735  
         }
 736  
 
 737  
         public boolean equals(Object obj)
 738  
         {
 739  
             if (obj instanceof Edge)
 740  
             {
 741  
                 Edge other = (Edge) obj;
 742  
                 return this.initialIdentity.equals(other.initialIdentity)
 743  
                         && this.terminalIdentity.equals(other.terminalIdentity);
 744  
             }
 745  
             else
 746  
             {
 747  
                 return false;
 748  
             }
 749  
         }
 750  
 
 751  
         public int hashCode()
 752  
         {
 753  
             return hashCode;
 754  
         }
 755  
 
 756  
         public String toString()
 757  
         {
 758  
             return new ToStringBuilder(this)
 759  
                     .append("initial", initialIdentity)
 760  
                     .append("terminal", terminalIdentity)
 761  
                     .append("weight", weight)
 762  
                     .append("processed", knownToBeProcessed)
 763  
                     .toString();
 764  
         }
 765  
     }
 766  
 
 767  
     /**
 768  
      * Represents a vertex in the object envelope graph
 769  
      */
 770  
     private static class Vertex
 771  
     {
 772  
         private ObjectEnvelope envelope;
 773  
         private boolean processed;
 774  
         private int incomingEdgeWeight;
 775  
 
 776  
         public Vertex(ObjectEnvelope envelope)
 777  
         {
 778  
             this.envelope = envelope;
 779  
             this.incomingEdgeWeight = 0;
 780  
             this.processed = false;
 781  
         }
 782  
 
 783  
         public ObjectEnvelope getEnvelope()
 784  
         {
 785  
             return envelope;
 786  
         }
 787  
 
 788  
         public void markProcessed()
 789  
         {
 790  
             processed = true;
 791  
         }
 792  
 
 793  
         public boolean isProcessed()
 794  
         {
 795  
             return processed;
 796  
         }
 797  
 
 798  
         public void resetIncomingEdgeWeight()
 799  
         {
 800  
             incomingEdgeWeight = 0;
 801  
         }
 802  
 
 803  
         public void incrementIncomingEdgeWeight(int weight)
 804  
         {
 805  
             incomingEdgeWeight += weight;
 806  
         }
 807  
 
 808  
         public int getIncomingEdgeWeight()
 809  
         {
 810  
             return incomingEdgeWeight;
 811  
         }
 812  
 
 813  
         public String toString()
 814  
         {
 815  
             return new ToStringBuilder(this)
 816  
                     .append("identity", envelope.getIdentity())
 817  
                     .append("processed", processed)
 818  
                     .append("incomingEdgeWeight", incomingEdgeWeight)
 819  
                     .toString();
 820  
         }
 821  
     }
 822  
 
 823  
 }