Home » apache-openjpa-1.1.0-source » org.apache.openjpa.jdbc » kernel » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
   10    * http://www.apache.org/licenses/LICENSE-2.0
   11    *
   12    * Unless required by applicable law or agreed to in writing,
   13    * software distributed under the License is distributed on an
   14    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   15    * KIND, either express or implied.  See the License for the
   16    * specific language governing permissions and limitations
   17    * under the License.
   18    */
   19   package org.apache.openjpa.jdbc.kernel;
   20   
   21   import java.sql.Connection;
   22   import java.sql.SQLException;
   23   import java.util.Collection;
   24   import java.util.HashMap;
   25   import java.util.Iterator;
   26   import java.util.LinkedList;
   27   import java.util.List;
   28   import java.util.Map;
   29   
   30   import org.apache.openjpa.jdbc.meta.ClassMapping;
   31   import org.apache.openjpa.jdbc.schema.Column;
   32   import org.apache.openjpa.jdbc.schema.ForeignKey;
   33   import org.apache.openjpa.jdbc.schema.Table;
   34   import org.apache.openjpa.jdbc.sql.PrimaryRow;
   35   import org.apache.openjpa.jdbc.sql.Row;
   36   import org.apache.openjpa.jdbc.sql.RowImpl;
   37   import org.apache.openjpa.jdbc.sql.RowManager;
   38   import org.apache.openjpa.jdbc.sql.RowManagerImpl;
   39   import org.apache.openjpa.jdbc.sql.SQLExceptions;
   40   import org.apache.openjpa.kernel.OpenJPAStateManager;
   41   import org.apache.openjpa.lib.graph.DepthFirstAnalysis;
   42   import org.apache.openjpa.lib.graph.Edge;
   43   import org.apache.openjpa.lib.graph.Graph;
   44   import org.apache.openjpa.lib.util.Localizer;
   45   import org.apache.openjpa.util.InternalException;
   46   import org.apache.openjpa.util.OpenJPAException;
   47   import org.apache.openjpa.util.UserException;
   48   
   49   /**
   50    * <p>Standard update manager, capable of foreign key constraint evaluation.</p>
   51    *
   52    * @since 1.0.0
   53    */
   54   public class ConstraintUpdateManager
   55       extends AbstractUpdateManager {
   56   
   57       private static final Localizer _loc = Localizer.forPackage
   58           (ConstraintUpdateManager.class);
   59   
   60       public boolean orderDirty() {
   61           return false;
   62       }
   63   
   64       protected PreparedStatementManager newPreparedStatementManager
   65           (JDBCStore store, Connection conn) {
   66           return new PreparedStatementManagerImpl(store, conn);
   67       }
   68   
   69       protected RowManager newRowManager() {
   70           return new RowManagerImpl(false);
   71       }
   72   
   73       protected Collection flush(RowManager rowMgr,
   74           PreparedStatementManager psMgr, Collection exceps) {
   75           RowManagerImpl rmimpl = (RowManagerImpl) rowMgr;
   76   
   77           // first take care of all secondary table deletes and 'all row' deletes
   78           // (which are probably secondary table deletes), since no foreign
   79           // keys ever rely on secondary table pks
   80           flush(rmimpl.getAllRowDeletes(), psMgr);
   81           flush(rmimpl.getSecondaryDeletes(), psMgr);
   82   
   83           // now do any 'all row' updates
   84           flush(rmimpl.getAllRowUpdates(), psMgr);
   85   
   86           // analyze foreign keys
   87           Collection inserts = rmimpl.getInserts();
   88           Collection updates = rmimpl.getUpdates();
   89           Collection deletes = rmimpl.getDeletes();
   90           Graph[] graphs = new Graph[2];    // insert graph, delete graph
   91           analyzeForeignKeys(inserts, updates, deletes, rmimpl, graphs);
   92   
   93           // flush insert graph, if any
   94           boolean autoAssign = rmimpl.hasAutoAssignConstraints();
   95           try {
   96               flushGraph(graphs[0], psMgr, autoAssign);
   97           } catch (SQLException se) {
   98               exceps = addException(exceps, SQLExceptions.getStore(se, dict));
   99           } catch (OpenJPAException ke) {
  100               exceps = addException(exceps, ke);
  101           }
  102   
  103           // flush the rest of the inserts and updates; inserts before updates
  104           // because some update fks might reference pks that have to be inserted
  105           flush(inserts, psMgr);
  106           flush(updates, psMgr);
  107   
  108           // flush the delete graph, if any
  109           try {
  110               flushGraph(graphs[1], psMgr, autoAssign);
  111           } catch (SQLException se) {
  112               exceps = addException(exceps, SQLExceptions.getStore(se, dict));
  113           } catch (OpenJPAException ke) {
  114               exceps = addException(exceps, ke);
  115           }
  116   
  117           // put the remainder of the deletes after updates because some updates
  118           // may be nulling fks to rows that are going to be deleted
  119           flush(deletes, psMgr);
  120   
  121           // take care of all secondary table inserts and updates last, since
  122           // they may rely on previous inserts or updates, but nothing relies
  123           // on them
  124           flush(rmimpl.getSecondaryUpdates(), psMgr);
  125   
  126           // flush any left over prepared statements
  127           psMgr.flush();
  128           return exceps;
  129       }
  130   
  131       /**
  132        * Analyze foreign key dependencies on the given rows
  133        * and create an insert and a delete graph to execute.  The insert
  134        * graph will be flushed before all other rows, and the delete graph will
  135        * be flushed after them.
  136        */
  137       private void analyzeForeignKeys(Collection inserts, Collection updates,
  138           Collection deletes, RowManagerImpl rowMgr, Graph[] graphs) {
  139           // if there are any deletes, we have to map the insert objects on their
  140           // oids so we'll be able to detect delete-then-insert-same-pk cases
  141           Map insertMap = null;
  142           OpenJPAStateManager sm;
  143           if (!deletes.isEmpty() && !inserts.isEmpty()) {
  144               insertMap = new HashMap((int) (inserts.size() * 1.33 + 1));
  145               for (Iterator itr = inserts.iterator(); itr.hasNext();) {
  146                   sm = ((Row) itr.next()).getPrimaryKey();
  147                   if (sm != null && sm.getObjectId() != null)
  148                       insertMap.put(sm.getObjectId(), sm);
  149               }
  150           }
  151   
  152           // first construct the graph for deletes; this may expand to include
  153           // inserts and updates as well if there are any inserts that rely on
  154           // deletes (delete-then-insert-same-pk cases)
  155           PrimaryRow row;
  156           Row row2;
  157           ForeignKey[] fks;
  158           OpenJPAStateManager fkVal;
  159           boolean ignoreUpdates = true;
  160           for (Iterator itr = deletes.iterator(); itr.hasNext();) {
  161               row = (PrimaryRow) itr.next();
  162               if (!row.isValid())
  163                   continue;
  164   
  165               row2 = getInsertRow(insertMap, rowMgr, row);
  166               if (row2 != null) {
  167                   ignoreUpdates = false;
  168                   graphs[1] = addEdge(graphs[1], (PrimaryRow) row2, row, null);
  169               }
  170   
  171               // now check this row's fks against other deletes
  172               fks = row.getTable().getForeignKeys();
  173               for (int j = 0; j < fks.length; j++) {
  174                   // when deleting ref fks they'll just set a where value, so
  175                   // check both for fk updates (relation fks) and wheres (ref fks)
  176                   fkVal = row.getForeignKeySet(fks[j]);
  177                   if (fkVal == null)
  178                       fkVal = row.getForeignKeyWhere(fks[j]);
  179                   if (fkVal == null)
  180                       continue;
  181   
  182                   row2 = rowMgr.getRow(fks[j].getPrimaryKeyTable(),
  183                       Row.ACTION_DELETE, fkVal, false);
  184                   if (row2 != null && row2.isValid() && row2 != row)
  185                       graphs[1] = addEdge(graphs[1], (PrimaryRow) row2, row,
  186                           fks[j]);
  187               }
  188           }
  189   
  190           if (ignoreUpdates)
  191               graphs[0] = analyzeAgainstInserts(inserts, rowMgr, graphs[0]);
  192           else {
  193               // put inserts *and updates* in the delete graph; they all rely
  194               // on each other
  195               graphs[1] = analyzeAgainstInserts(updates, rowMgr, graphs[1]);
  196               graphs[1] = analyzeAgainstInserts(inserts, rowMgr, graphs[1]);
  197           }
  198       }
  199   
  200       /**
  201        * Check to see if there is an insert for for the same table and primary
  202        * key values as the given delete row.
  203        */
  204       private Row getInsertRow(Map insertMap, RowManagerImpl rowMgr, Row row) {
  205           if (insertMap == null)
  206               return null;
  207   
  208           OpenJPAStateManager sm = row.getPrimaryKey();
  209           if (sm == null)
  210               return null;
  211   
  212           // look for a new object whose insert id is the same as this delete one
  213           Object oid = sm.getObjectId();
  214           OpenJPAStateManager nsm = (OpenJPAStateManager) insertMap.get(oid);
  215           if (nsm == null)
  216               return null;
  217   
  218           // found new object; get its row
  219           row = rowMgr.getRow(row.getTable(), Row.ACTION_INSERT, nsm, false);
  220           return (row == null || row.isValid()) ? row : null;
  221       }
  222   
  223       /**
  224        * Analyze the given rows against the inserts, placing dependencies
  225        * in the given graph.
  226        */
  227       private Graph analyzeAgainstInserts(Collection rows, RowManagerImpl rowMgr,
  228           Graph graph) {
  229           PrimaryRow row;
  230           Row row2;
  231           ForeignKey[] fks;
  232           Column[] cols;
  233           for (Iterator itr = rows.iterator(); itr.hasNext();) {
  234               row = (PrimaryRow) itr.next();
  235               if (!row.isValid())
  236                   continue;
  237   
  238               // check this row's fks against inserts; a logical fk to an auto-inc
  239               // column is treated just as actual database fk because the result
  240               // is the same: the pk row has to be inserted before the fk row
  241               fks = row.getTable().getForeignKeys();
  242               for (int j = 0; j < fks.length; j++) {
  243                   if (row.getForeignKeySet(fks[j]) == null)
  244                       continue;
  245   
  246                   // see if this row is dependent on another.  if it's only
  247                   // depenent on itself, see if the fk is logical or deferred, in
  248                   // which case it must be an auto-inc because otherwise we
  249                   // wouldn't have recorded it
  250                   row2 = rowMgr.getRow(fks[j].getPrimaryKeyTable(),
  251                       Row.ACTION_INSERT, row.getForeignKeySet(fks[j]), false);
  252                   if (row2 != null && row2.isValid() && (row2 != row
  253                       || fks[j].isDeferred() || fks[j].isLogical()))
  254                       graph = addEdge(graph, row, (PrimaryRow) row2, fks[j]);
  255               }
  256   
  257               // see if there are any relation id columns dependent on
  258               // auto-inc objects
  259               cols = row.getTable().getRelationIdColumns();
  260               for (int j = 0; j < cols.length; j++) {
  261                   OpenJPAStateManager sm = row.getRelationIdSet(cols[j]);
  262                   if (sm == null)
  263                       continue;
  264   
  265                   row2 = rowMgr.getRow(getBaseTable(sm), Row.ACTION_INSERT,
  266                       sm, false);
  267                   if (row2 != null && row2.isValid())
  268                       graph = addEdge(graph, row, (PrimaryRow) row2, cols[j]);
  269               }
  270           }
  271           return graph;
  272       }
  273   
  274       /**
  275        * Return the base table for the given instance.
  276        */
  277       private static Table getBaseTable(OpenJPAStateManager sm) {
  278           ClassMapping cls = (ClassMapping) sm.getMetaData();
  279           while (cls.getJoinablePCSuperclassMapping() != null)
  280               cls = cls.getJoinablePCSuperclassMapping();
  281           return cls.getTable();
  282       }
  283   
  284       /**
  285        * Add an edge between the given rows in the given foreign key graph.
  286        */
  287       private Graph addEdge(Graph graph, PrimaryRow row1, PrimaryRow row2,
  288           Object fk) {
  289           // delay creation of the graph
  290           if (graph == null)
  291               graph = new Graph();
  292   
  293           row1.setDependent(true);
  294           row2.setDependent(true);
  295           graph.addNode(row1);
  296           graph.addNode(row2);
  297   
  298           // add an edge from row1 to row2, and set the fk causing the
  299           // dependency as the user object so we can retrieve it when resolving
  300           // circular constraints
  301           Edge edge = new Edge(row1, row2, true);
  302           edge.setUserObject(fk);
  303           graph.addEdge(edge);
  304   
  305           return graph;
  306       }
  307   
  308       /**
  309        * Flush the given graph of rows in the proper order.
  310        * @param graph The graph of statements to be walked
  311        * @param psMgr The prepared statement manager to use to issue the
  312        * statements
  313        * @param autoAssign Whether any of the rows in the graph have any
  314        * auto-assign constraints
  315        */
  316       protected void flushGraph(Graph graph, PreparedStatementManager psMgr,
  317           boolean autoAssign)
  318           throws SQLException {
  319           if (graph == null)
  320               return;
  321   
  322           DepthFirstAnalysis dfa = newDepthFirstAnalysis(graph, autoAssign);
  323           Collection insertUpdates = new LinkedList();
  324           Collection deleteUpdates = new LinkedList();
  325           boolean recalculate;
  326   
  327           // Handle circular constraints:
  328           // - if deleted row A has a ciricular fk to deleted row B, 
  329           //   then use an update statement to null A's fk to B before flushing, 
  330           //   and then flush
  331           // - if inserted row A has a circular fk to updated/inserted row B,
  332           //   then null the fk in the B row object, then flush,
  333           //   and after flushing, use an update to set the fk back to A
  334           // Depending on where circular dependencies are broken, the  
  335           // topological order of the graph nodes has to be re-calculated.
  336           recalculate = resolveCycles(graph, dfa.getEdges(Edge.TYPE_BACK),
  337                   deleteUpdates, insertUpdates);
  338           recalculate |= resolveCycles(graph, dfa.getEdges(Edge.TYPE_FORWARD),
  339                   deleteUpdates, insertUpdates);
  340   
  341           if (recalculate) {
  342               dfa = recalculateDepthFirstAnalysis(graph, autoAssign);
  343           }
  344   
  345           // flush delete updates to null fks, then all rows in order, then
  346           // the insert updates to set circular fk values
  347           flush(deleteUpdates, psMgr);
  348           Collection nodes = dfa.getSortedNodes();
  349           for (Iterator itr = nodes.iterator(); itr.hasNext();)
  350               psMgr.flush((RowImpl) itr.next());
  351           flush(insertUpdates, psMgr);
  352       }
  353   
  354       /**
  355        * Break a circular dependency caused by delete operations.
  356        * If deleted row A has a ciricular fk to deleted row B, then use an update 
  357        * statement to null A's fk to B before deleting B, then delete A.
  358        * @param edge Edge in the dependency graph corresponding to a foreign key
  359        * constraint. This dependency is broken by nullifying the foreign key.
  360        * @param deleteUpdates Collection of update statements that are executed
  361        * before the delete operations are flushed 
  362        */
  363       private void addDeleteUpdate(Edge edge, Collection deleteUpdates)
  364           throws SQLException {
  365           PrimaryRow row;
  366           RowImpl update;
  367           ForeignKey fk;
  368   
  369           // copy where conditions into new update that nulls the fk
  370           row = (PrimaryRow) edge.getTo();
  371           update = new PrimaryRow(row.getTable(), Row.ACTION_UPDATE, null);
  372           row.copyInto(update, true);
  373           if (edge.getUserObject() instanceof ForeignKey) {
  374               fk = (ForeignKey) edge.getUserObject();
  375               update.setForeignKey(fk, row.getForeignKeyIO(fk), null);
  376           } else
  377               update.setNull((Column) edge.getUserObject());
  378   
  379           deleteUpdates.add(update);
  380       }
  381   
  382       /**
  383        * Break a circular dependency caused by insert operations.
  384        * If inserted row A has a circular fk to updated/inserted row B,
  385        * then null the fk in the B row object, then flush,
  386        * and after flushing, use an update to set the fk back to A.
  387        * @param row Row to be flushed
  388        * @param edge Edge in the dependency graph corresponding to a foreign key
  389        * constraint. This dependency is broken by nullifying the foreign key.
  390        * @param insertUpdates Collection of update statements that are executed
  391        * after the insert/update operations are flushed 
  392        */
  393       private void addInsertUpdate(PrimaryRow row, Edge edge,
  394           Collection insertUpdates) throws SQLException {
  395           RowImpl update;
  396           ForeignKey fk;
  397           Column col;
  398   
  399           // copy where conditions into new update that sets the fk
  400           update = new PrimaryRow(row.getTable(), Row.ACTION_UPDATE, null);
  401           if (row.getAction() == Row.ACTION_INSERT) {
  402               if (row.getPrimaryKey() == null)
  403                   throw new InternalException(_loc.get("ref-cycle"));
  404               update.wherePrimaryKey(row.getPrimaryKey());
  405           } else {
  406               // Row.ACTION_UPDATE
  407               row.copyInto(update, true);
  408           }
  409           if (edge.getUserObject() instanceof ForeignKey) {
  410               fk = (ForeignKey) edge.getUserObject();
  411               update.setForeignKey(fk, row.getForeignKeyIO(fk),
  412                   row.getForeignKeySet(fk));
  413               row.clearForeignKey(fk);
  414           } else {
  415               col = (Column) edge.getUserObject();
  416               update.setRelationId(col, row.getRelationIdSet(col),
  417                   row.getRelationIdCallback(col));
  418               row.clearRelationId(col);
  419           }
  420   
  421           insertUpdates.add(update);
  422       }
  423   
  424       /**
  425        * Finds a nullable foreign key by walking the dependency cycle. 
  426        * Circular dependencies can be broken at this point.
  427        * @param cycle Cycle in the dependency graph.
  428        * @return Edge corresponding to a nullable foreign key.
  429        */
  430       private Edge findBreakableLink(List cycle) {
  431           Edge breakableLink = null;
  432           for (Iterator iter = cycle.iterator(); iter.hasNext(); ) {
  433               Edge edge = (Edge) iter.next();
  434               Object userObject = edge.getUserObject();
  435               if (userObject instanceof ForeignKey) {
  436                    if (!((ForeignKey) userObject).hasNotNullColumns()) {
  437                        breakableLink = edge;
  438                        break;
  439                    }
  440               } else if (userObject instanceof Column) {
  441                   if (!((Column) userObject).isNotNull()) {
  442                       breakableLink = edge;
  443                       break;
  444                   }
  445               }
  446           }
  447           return breakableLink;
  448       }
  449   
  450       /**
  451        * Re-calculates the DepthFirstSearch analysis of the graph 
  452        * after some of the edges have been removed. Ensures
  453        * that the dependency graph is cycle free.
  454        * @param graph The graph of statements to be walked
  455        * @param autoAssign Whether any of the rows in the graph have any
  456        * auto-assign constraints
  457        */
  458       private DepthFirstAnalysis recalculateDepthFirstAnalysis(Graph graph,
  459           boolean autoAssign) {
  460           DepthFirstAnalysis dfa;
  461           // clear previous traversal data
  462           graph.clearTraversal();
  463           dfa = newDepthFirstAnalysis(graph, autoAssign);
  464           // make sure that the graph is non-cyclic now
  465           assert (dfa.hasNoCycles()): _loc.get("graph-not-cycle-free");
  466           return dfa;
  467       }
  468   
  469       /**
  470        * Resolve circular dependencies by identifying and breaking
  471        * a nullable foreign key.
  472        * @param graph Dependency graph.
  473        * @param edges Collection of edges. Each edge indicates a possible 
  474        * circular dependency
  475        * @param deleteUpdates Collection of update operations (nullifying 
  476        * foreign keys) to be filled. These updates will be executed before 
  477        * the rows in the dependency graph are flushed
  478        * @param insertUpdates CCollection of update operations (nullifying 
  479        * foreign keys) to be filled. These updates will be executed after 
  480        * the rows in the dependency graph are flushed
  481        * @return Depending on where circular dependencies are broken, the  
  482        * topological order of the graph nodes has to be re-calculated.
  483        */
  484       private boolean resolveCycles(Graph graph, Collection edges,
  485           Collection deleteUpdates, Collection insertUpdates)
  486           throws SQLException {
  487           boolean recalculate = false;
  488           for (Iterator itr = edges.iterator(); itr.hasNext();) {
  489               Edge edge = (Edge) itr.next();
  490               List cycle = edge.getCycle();
  491   
  492               if (cycle != null) {
  493                   // find a nullable foreign key
  494                   Edge breakableLink = findBreakableLink(cycle);
  495                   if (breakableLink == null) {
  496                       throw new UserException(_loc.get("no-nullable-fk"));
  497                   }
  498   
  499                   // topologic node order must be re-calculated,  if the
  500                   // breakable link is different from the edge where
  501                   // the circular dependency was originally detected
  502                   if (edge != breakableLink) {
  503                       recalculate = true;
  504                   }
  505   
  506                   if (!breakableLink.isRemovedFromGraph()) {
  507   
  508                       // use a primary row update to prevent setting pk and fk values
  509                       // until after flush, to get latest auto-increment values
  510                       PrimaryRow row = (PrimaryRow) breakableLink.getFrom();
  511                       if (row.getAction() == Row.ACTION_DELETE) {
  512                           addDeleteUpdate(breakableLink, deleteUpdates);
  513                       } else {
  514                           addInsertUpdate(row, breakableLink, insertUpdates);
  515                       }
  516                       graph.removeEdge(breakableLink);
  517                   }
  518               }
  519           }
  520           return recalculate;
  521       }
  522   
  523       /**
  524        * Create a new {@link DepthFirstAnalysis} suitable for the given graph
  525        * and auto-assign settings.
  526        */
  527       protected DepthFirstAnalysis newDepthFirstAnalysis(Graph graph,
  528           boolean autoAssign) {
  529           return new DepthFirstAnalysis(graph);
  530       }
  531   
  532       /**
  533        * Flush the given collection of secondary rows.
  534        */
  535       protected void flush(Collection rows, PreparedStatementManager psMgr) {
  536           if (rows.size() == 0)
  537               return;
  538   
  539           RowImpl row;
  540           for (Iterator itr = rows.iterator(); itr.hasNext(); ) {
  541               row = (RowImpl) itr.next();
  542               if (row.isValid() && !row.isDependent())
  543                   psMgr.flush(row);
  544           }
  545       }
  546   }

Save This Page
Home » apache-openjpa-1.1.0-source » org.apache.openjpa.jdbc » kernel » [javadoc | source]