Home » apache-openjpa-1.1.0-source » org.apache.openjpa.jdbc » meta » strats » [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.meta.strats;
   20   
   21   import java.sql.SQLException;
   22   import java.util.ArrayList;
   23   import java.util.Collection;
   24   import java.util.HashMap;
   25   import java.util.Map;
   26   
   27   import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
   28   import org.apache.openjpa.jdbc.kernel.JDBCStore;
   29   import org.apache.openjpa.jdbc.meta.ClassMapping;
   30   import org.apache.openjpa.jdbc.meta.FieldMapping;
   31   import org.apache.openjpa.jdbc.meta.FieldStrategy;
   32   import org.apache.openjpa.jdbc.schema.Column;
   33   import org.apache.openjpa.jdbc.schema.ForeignKey;
   34   import org.apache.openjpa.jdbc.sql.Joins;
   35   import org.apache.openjpa.jdbc.sql.Result;
   36   import org.apache.openjpa.jdbc.sql.Select;
   37   import org.apache.openjpa.jdbc.sql.SelectExecutor;
   38   import org.apache.openjpa.jdbc.sql.Union;
   39   import org.apache.openjpa.kernel.OpenJPAStateManager;
   40   import org.apache.openjpa.meta.JavaTypes;
   41   import org.apache.openjpa.util.ChangeTracker;
   42   import org.apache.openjpa.util.Id;
   43   import org.apache.openjpa.util.Proxy;
   44   
   45   /**
   46    * Base class for strategies that are stored as a collection, even if
   47    * their field value is something else. Handles data loading and basic query
   48    * functionality. Subclasses must implement abstract methods and
   49    * insert/update/delete behavior as well as overriding
   50    * {@link FieldStrategy#toDataStoreValue}, {@link FieldStrategy#join}, and
   51    * {@link FieldStrategy#joinRelation} if necessary.
   52    *
   53    * @author Abe White
   54    */
   55   public abstract class StoreCollectionFieldStrategy
   56       extends ContainerFieldStrategy {
   57   
   58       /**
   59        * Return the foreign key used to join to the owning field for the given
   60        * element mapping from {@link #getIndependentElementMappings} (or null).
   61        */
   62       protected abstract ForeignKey getJoinForeignKey(ClassMapping elem);
   63   
   64       /**
   65        * Implement this method to select the elements of this field for the
   66        * given element mapping from {@link #getIndependentElementMappings}
   67        * (or null). Elements of the result will be loaded with
   68        * {@link #loadElement}.
   69        */
   70       protected abstract void selectElement(Select sel, ClassMapping elem,
   71           JDBCStore store, JDBCFetchConfiguration fetch, int eagerMode,
   72           Joins joins);
   73   
   74       /**
   75        * Load an element of the collection. The given state manager might be
   76        * null if the load is for a projection or for processing eager parallel
   77        * results.
   78        */
   79       protected abstract Object loadElement(OpenJPAStateManager sm,
   80           JDBCStore store, JDBCFetchConfiguration fetch, Result res, Joins joins)
   81           throws SQLException;
   82   
   83       /**
   84        * Join this value's table to the table for the given element mapping
   85        * from {@link #getIndependentElementMappings} (or null).
   86        *
   87        * @see FieldMapping#joinRelation
   88        */
   89       protected abstract Joins joinElementRelation(Joins joins,
   90           ClassMapping elem);
   91   
   92       /**
   93        * Join to the owning field table for the given element mapping from
   94        * {@link #getIndependentElementMappings} (or null).
   95        */
   96       protected abstract Joins join(Joins joins, ClassMapping elem);
   97   
   98       /**
   99        * Return a large result set proxy for this field.
  100        */
  101       protected abstract Proxy newLRSProxy();
  102   
  103       /**
  104        * Convert the field value to a collection. Handles collections and
  105        * arrays by default.
  106        */
  107       protected Collection toCollection(Object val) {
  108           if (field.getTypeCode() == JavaTypes.COLLECTION)
  109               return (Collection) val;
  110           return JavaTypes.toList(val, field.getElement().getType(), false);
  111       }
  112   
  113       /**
  114        * Add an item to the data structure representing a field value.
  115        * By default, assumes the structure is a collection.
  116        */
  117       protected void add(JDBCStore store, Object coll, Object obj) {
  118           ((Collection) coll).add(obj);
  119       }
  120   
  121       /**
  122        * Returns the first independent element mapping, or null.
  123        */
  124       private ClassMapping getDefaultElementMapping(boolean traverse) {
  125           ClassMapping[] elems = getIndependentElementMappings(traverse);
  126           return (elems.length == 0) ? null : elems[0];
  127       }
  128   
  129       public int supportsSelect(Select sel, int type, OpenJPAStateManager sm,
  130           JDBCStore store, JDBCFetchConfiguration fetch) {
  131           if (field.isLRS())
  132               return 0;
  133           if (type == Select.EAGER_PARALLEL)
  134               return Math.max(1, getIndependentElementMappings(true).length);
  135           if (type != Select.EAGER_INNER && type != Select.EAGER_OUTER)
  136               return 0;
  137           if (getIndependentElementMappings(true).length > 1)
  138               return 0;
  139           return (type == Select.EAGER_INNER || store.getDBDictionary().
  140               canOuterJoin(sel.getJoinSyntax(), getJoinForeignKey
  141                   (getDefaultElementMapping(false)))) ? 1 : 0;
  142       }
  143   
  144       public void selectEagerParallel(SelectExecutor sel,
  145           final OpenJPAStateManager sm, final JDBCStore store,
  146           final JDBCFetchConfiguration fetch, final int eagerMode) {
  147           if (!(sel instanceof Union))
  148               selectEager((Select) sel, getDefaultElementMapping(true), sm,
  149                   store, fetch, eagerMode, true, false);
  150           else {
  151               final ClassMapping[] elems = getIndependentElementMappings(true);
  152               Union union = (Union) sel;
  153               if (fetch.getSubclassFetchMode(field.getElementMapping().
  154                   getTypeMapping()) != fetch.EAGER_JOIN)
  155                   union.abortUnion();
  156               union.select(new Union.Selector() {
  157                   public void select(Select sel, int idx) {
  158                       selectEager(sel, elems[idx], sm, store, fetch, eagerMode, 
  159                           true, false);
  160                   }
  161               });
  162           }
  163       }
  164   
  165       public void selectEagerJoin(Select sel, OpenJPAStateManager sm,
  166           JDBCStore store, JDBCFetchConfiguration fetch, int eagerMode) {
  167           // we limit further eager fetches to joins, because after this point
  168           // the select has been modified such that parallel clones may produce
  169           // invalid sql
  170           boolean outer = field.getNullValue() != FieldMapping.NULL_EXCEPTION;
  171           // force inner join for inner join fetch 
  172           if (fetch.hasFetchInnerJoin(field.getFullName(false)))
  173               outer = false;
  174           selectEager(sel, getDefaultElementMapping(true), sm, store, fetch, 
  175               JDBCFetchConfiguration.EAGER_JOIN, false,
  176               outer);
  177       }
  178   
  179       public boolean isEagerSelectToMany() {
  180           return true;
  181       }
  182   
  183       /**
  184        * Select our data eagerly.
  185        */
  186       private void selectEager(Select sel, ClassMapping elem,
  187           OpenJPAStateManager sm, JDBCStore store, JDBCFetchConfiguration fetch,
  188           int eagerMode, boolean selectOid, boolean outer) {
  189           // force distinct if there was a to-many join to avoid dups, but
  190           // if this is a parallel select don't make distinct based on the
  191           // eager joins alone if the original wasn't distinct
  192           if (eagerMode == JDBCFetchConfiguration.EAGER_PARALLEL) {
  193               if (sel.hasJoin(true))
  194                   sel.setDistinct(true);
  195               else if (!sel.isDistinct())
  196                   sel.setDistinct(false); // set explicitly so remembered
  197           }
  198   
  199           // set a variable name that does not conflict with any in the query;
  200           // using a variable guarantees that the selected data will use different
  201           // aliases and joins than any existing WHERE conditions on this field
  202           // that might otherwise limit the elements of the field that match
  203           if (selectOid)
  204               sel.orderByPrimaryKey(field.getDefiningMapping(), true, true);
  205           Joins joins = sel.newJoins().setVariable("*");
  206           joins = join(joins, elem);
  207   
  208           // order, ref cols
  209           if (field.getOrderColumn() != null || field.getOrders().length > 0
  210               || !selectOid) {
  211               if (outer)
  212                   joins = sel.outer(joins);
  213               if (!selectOid) {
  214                   Column[] refs = getJoinForeignKey(elem).getColumns();
  215                   sel.orderBy(refs, true, joins, true);
  216               }
  217               field.orderLocal(sel, elem, joins);
  218           }
  219   
  220           // select data
  221           joins = joinElementRelation(joins, elem);
  222           if (outer)
  223               joins = sel.outer(joins);
  224           field.orderRelation(sel, elem, joins);
  225           selectElement(sel, elem, store, fetch, eagerMode, joins);
  226       }
  227   
  228       public Object loadEagerParallel(OpenJPAStateManager sm, JDBCStore store,
  229           JDBCFetchConfiguration fetch, Object res)
  230           throws SQLException {
  231           // process batched results if we haven't already
  232           Map rels;
  233           if (res instanceof Result)
  234               rels = processEagerParallelResult(sm, store, fetch, (Result) res);
  235           else
  236               rels = (Map) res;
  237   
  238           // look up the collection for this oid, and store in instance
  239           Object coll = rels.remove(sm.getObjectId());
  240           if (field.getTypeCode() == JavaTypes.ARRAY)
  241               sm.storeObject(field.getIndex(), JavaTypes.toArray
  242                   ((Collection) coll, field.getElement().getType()));
  243           else {
  244               if (coll == null)
  245                   coll = sm.newProxy(field.getIndex());
  246               sm.storeObject(field.getIndex(), coll);
  247           }
  248           return rels;
  249       }
  250   
  251       /**
  252        * Process the given batched result.
  253        */
  254       private Map processEagerParallelResult(OpenJPAStateManager sm,
  255           JDBCStore store, JDBCFetchConfiguration fetch, Result res)
  256           throws SQLException {
  257           // do same joins as for load
  258           //### cheat: we know typical result joins only care about the relation
  259           //### path; thus we can ignore different mappings
  260           ClassMapping elem = getDefaultElementMapping(true);
  261           Joins dataJoins = res.newJoins().setVariable("*");
  262           dataJoins = join(dataJoins, elem);
  263           dataJoins = joinElementRelation(dataJoins, elem);
  264           Joins orderJoins = null;
  265           if (field.getOrderColumn() != null) {
  266               orderJoins = res.newJoins().setVariable("*");
  267               orderJoins = join(orderJoins, elem);
  268           }
  269   
  270           Map rels = new HashMap();
  271           ClassMapping ownerMapping = field.getDefiningMapping();
  272           Object nextOid, oid = null;
  273           Object coll = null;
  274           int seq = 0;
  275           while (res.next()) {
  276               // extract the owner id value
  277               nextOid = getNextObjectId(ownerMapping, store, res, oid);
  278               if (nextOid != oid) {
  279                   // if the old coll was an ordered tracking proxy, set
  280                   // its seq value to the last order val we read
  281                   if (seq != 0 && coll instanceof Proxy)
  282                       ((Proxy) coll).getChangeTracker().setNextSequence(seq);
  283   
  284                   // start a new collection
  285                   oid = nextOid;
  286                   seq = 0;
  287                   if (field.getTypeCode() == JavaTypes.ARRAY)
  288                       coll = new ArrayList();
  289                   else
  290                       coll = sm.newProxy(field.getIndex());
  291                   rels.put(oid, coll);
  292               }
  293   
  294               if (field.getOrderColumn() != null)
  295                   seq = res.getInt(field.getOrderColumn(), orderJoins) + 1;
  296               add(store, coll, loadElement(null, store, fetch, res, dataJoins));
  297           }
  298           res.close();
  299   
  300           return rels;
  301       }
  302   
  303       /**
  304        * Extract the oid value from the given result. If the next oid is the
  305        * same as the given one, returns the given JVM instance.
  306        */
  307       private Object getNextObjectId(ClassMapping owner, JDBCStore store,
  308           Result res, Object oid)
  309           throws SQLException {
  310           // if this is a datastore id class we can avoid creating a new oid
  311           // object for the common case
  312           if (oid != null && owner.getIdentityType() == ClassMapping.ID_DATASTORE
  313               && owner.isPrimaryKeyObjectId(true)) {
  314               long nid = res.getLong(owner.getPrimaryKeyColumns()[0]);
  315               long id = ((Id) oid).getId();
  316               return (nid == id) ? oid : store.newDataStoreId(nid, owner, true);
  317           }
  318   
  319           Object noid = owner.getObjectId(store, res, null, true, null);
  320           if (noid == null)
  321               return null;
  322           return (noid.equals(oid)) ? oid : noid;
  323       }
  324   
  325       public void loadEagerJoin(OpenJPAStateManager sm, JDBCStore store,
  326           JDBCFetchConfiguration fetch, Result res)
  327           throws SQLException {
  328           // initialize field value
  329           Object coll;
  330           if (field.getTypeCode() == JavaTypes.ARRAY)
  331               coll = new ArrayList();
  332           else
  333               coll = sm.newProxy(field.getIndex());
  334   
  335           Joins dataJoins = null;
  336           Joins refJoins = res.newJoins().setVariable("*");
  337           join(refJoins, false);
  338   
  339           ClassMapping ownerMapping = field.getDefiningMapping();
  340           Object ref = null;
  341           int seq = 0;
  342           int typeIdx = res.indexOf();
  343           for (int i = 0; true; i++) {
  344               // extract the owner id value
  345               ref = getNextRef(ownerMapping, store, res, ref, refJoins);
  346               if (ref == null) {
  347                   // if the old coll was an ordered tracking proxy, set
  348                   // its seq value to the last order val we read
  349                   if (seq != 0 && coll instanceof Proxy)
  350                       ((Proxy) coll).getChangeTracker().setNextSequence(seq);
  351                   if (i != 0)
  352                       res.pushBack();
  353                   break;
  354               }
  355   
  356               // do same joins as for load
  357               if (dataJoins == null) {
  358                   dataJoins = res.newJoins().setVariable("*");
  359                   dataJoins = join(dataJoins, false);
  360                   dataJoins = joinRelation(dataJoins, false, false);
  361               }
  362   
  363               if (field.getOrderColumn() != null)
  364                   seq = res.getInt(field.getOrderColumn(), refJoins) + 1;
  365               res.setBaseMapping(null);
  366               add(store, coll, loadElement(sm, store, fetch, res, dataJoins));
  367               if (!res.next() || res.indexOf() != typeIdx) {
  368                   res.pushBack();
  369                   break;
  370               }
  371           }
  372   
  373           // load the collection into the object
  374           if (field.getTypeCode() == JavaTypes.ARRAY)
  375               sm.storeObject(field.getIndex(), JavaTypes.toArray
  376                   ((Collection) coll, field.getElement().getType()));
  377           else
  378               sm.storeObject(field.getIndex(), coll);
  379       }
  380   
  381       /**
  382        * Extract the reference column value(s) from the given result. If the
  383        * extracted result is the same as the current one or the current
  384        * one is null, returns the extracted result. Else returns null.
  385        */
  386       private Object getNextRef(ClassMapping mapping, JDBCStore store,
  387           Result res, Object ref, Joins refJoins)
  388           throws SQLException {
  389           Column[] cols = getJoinForeignKey(getDefaultElementMapping(false)).
  390               getColumns();
  391           Object val;
  392           if (cols.length == 1) {
  393               val = res.getObject(cols[0], null, refJoins);
  394               if (val == null || (ref != null && !val.equals(ref)))
  395                   return null;
  396               return val;
  397           }
  398   
  399           Object[] refs = (Object[]) ref;
  400           if (refs == null)
  401               refs = new Object[cols.length];
  402           for (int i = 0; i < cols.length; i++) {
  403               val = res.getObject(cols[i], null, refJoins);
  404               if (val == null)
  405                   return null;
  406               if (refs[i] != null && !val.equals(refs[i]))
  407                   return null;
  408               refs[i] = val;
  409           }
  410           return refs;
  411       }
  412   
  413       public void load(final OpenJPAStateManager sm, final JDBCStore store,
  414           final JDBCFetchConfiguration fetch)
  415           throws SQLException {
  416           if (field.isLRS()) {
  417               Proxy coll = newLRSProxy();
  418   
  419               // if this is ordered we need to know the next seq to use in case
  420               // objects are added to the collection
  421               if (field.getOrderColumn() != null) {
  422                   // we don't allow ordering table per class one-many's, so
  423                   // we know we don't need a union
  424                   Select sel = store.getSQLFactory().newSelect();
  425                   sel.setAggregate(true);
  426                   StringBuffer sql = new StringBuffer();
  427                   sql.append("MAX(").
  428                       append(sel.getColumnAlias(field.getOrderColumn())).
  429                       append(")");
  430                   sel.select(sql.toString(), field);
  431                   ClassMapping rel = getDefaultElementMapping(false);
  432                   sel.whereForeignKey(getJoinForeignKey(rel),
  433                       sm.getObjectId(), field.getDefiningMapping(), store);
  434   
  435                   Result res = sel.execute(store, fetch);
  436                   try {
  437                       res.next();
  438                       coll.getChangeTracker().setNextSequence
  439                           (res.getInt(field) + 1);
  440                   } finally {
  441                       res.close();
  442                   }
  443               }
  444               sm.storeObjectField(field.getIndex(), coll);
  445               return;
  446           }
  447   
  448           // select data for this sm
  449           final ClassMapping[] elems = getIndependentElementMappings(true);
  450           final Joins[] resJoins = new Joins[Math.max(1, elems.length)];
  451           Union union = store.getSQLFactory().newUnion
  452               (Math.max(1, elems.length));
  453           union.select(new Union.Selector() {
  454               public void select(Select sel, int idx) {
  455                   ClassMapping elem = (elems.length == 0) ? null : elems[idx];
  456                   resJoins[idx] = selectAll(sel, elem, sm, store, fetch,
  457                       JDBCFetchConfiguration.EAGER_PARALLEL);
  458               }
  459           });
  460   
  461           // create proxy
  462           Object coll;
  463           ChangeTracker ct = null;
  464           if (field.getTypeCode() == JavaTypes.ARRAY)
  465               coll = new ArrayList();
  466           else {
  467               coll = sm.newProxy(field.getIndex());
  468               if (coll instanceof Proxy)
  469                   ct = ((Proxy) coll).getChangeTracker();
  470           }
  471   
  472           // load values
  473           Result res = union.execute(store, fetch);
  474           try {
  475               int seq = -1;
  476               while (res.next()) {
  477                   if (ct != null && field.getOrderColumn() != null)
  478                       seq = res.getInt(field.getOrderColumn());
  479                   add(store, coll, loadElement(sm, store, fetch, res,
  480                       resJoins[res.indexOf()]));
  481               }
  482               if (ct != null && field.getOrderColumn() != null)
  483                   ct.setNextSequence(seq + 1);
  484           } finally {
  485               res.close();
  486           }
  487   
  488           // set into sm
  489           if (field.getTypeCode() == JavaTypes.ARRAY)
  490               sm.storeObject(field.getIndex(), JavaTypes.toArray
  491                   ((Collection) coll, field.getElement().getType()));
  492           else
  493               sm.storeObject(field.getIndex(), coll);
  494       }
  495   
  496       /**
  497        * Select data for loading, starting in field table.
  498        */
  499       protected Joins selectAll(Select sel, ClassMapping elem,
  500           OpenJPAStateManager sm, JDBCStore store, JDBCFetchConfiguration fetch,
  501           int eagerMode) {
  502           sel.whereForeignKey(getJoinForeignKey(elem), sm.getObjectId(),
  503               field.getDefiningMapping(), store);
  504   
  505           // order first, then select so that if the projection introduces
  506           // additional ordering, it will be after our required ordering
  507           field.orderLocal(sel, elem, null);
  508           Joins joins = joinElementRelation(sel.newJoins(), elem);
  509           field.orderRelation(sel, elem, joins);
  510           selectElement(sel, elem, store, fetch, eagerMode, joins);
  511           return joins;
  512       }
  513   
  514       public Object loadProjection(JDBCStore store, JDBCFetchConfiguration fetch,
  515           Result res, Joins joins)
  516           throws SQLException {
  517           return loadElement(null, store, fetch, res, joins);
  518       }
  519   
  520       protected ForeignKey getJoinForeignKey() {
  521           return getJoinForeignKey(getDefaultElementMapping(false));
  522       }
  523   }

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