Home » apache-openjpa-1.1.0-source » org.apache.openjpa » 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.kernel;
   20   
   21   import java.lang.reflect.Array;
   22   import java.util.Collection;
   23   import java.util.Iterator;
   24   import java.util.Map;
   25   import java.util.Set;
   26   
   27   import org.apache.commons.lang.ObjectUtils;
   28   import org.apache.openjpa.enhance.PersistenceCapable;
   29   import org.apache.openjpa.enhance.StateManager;
   30   import org.apache.openjpa.lib.util.Localizer;
   31   import org.apache.openjpa.meta.ClassMetaData;
   32   import org.apache.openjpa.meta.FieldMetaData;
   33   import org.apache.openjpa.meta.JavaTypes;
   34   import org.apache.openjpa.meta.ValueMetaData;
   35   import org.apache.openjpa.util.Exceptions;
   36   import org.apache.openjpa.util.InternalException;
   37   import org.apache.openjpa.util.UserException;
   38   
   39   /**
   40    * Strategy for attaching objects.
   41    *
   42    * @author Marc Prud'hommeaux
   43    * @author Steve Kim
   44    * @nojavadoc
   45    */
   46   abstract class AttachStrategy
   47       extends TransferFieldManager {
   48   
   49       private static final Localizer _loc = Localizer.forPackage
   50           (AttachStrategy.class);
   51   
   52       /**
   53        * Attach.
   54        *
   55        * @param manager manager holding cache of attached instances
   56        * @param toAttach detached instance
   57        * @param meta metadata for the instance being attached
   58        * @param into instance we're attaching into
   59        * @param owner state manager for <code>into</code>
   60        * @param ownerMeta field we traversed to find <code>toAttach</code>
   61        * @param explicit whether to make new instances explicitly persistent
   62        */
   63       public abstract Object attach(AttachManager manager,
   64           Object toAttach, ClassMetaData meta, PersistenceCapable into,
   65           OpenJPAStateManager owner, ValueMetaData ownerMeta, boolean explicit);
   66   
   67       /**
   68        * Return the identity of the given detached instance.
   69        */
   70       protected abstract Object getDetachedObjectId(AttachManager manager,
   71           Object toAttach);
   72   
   73       /**
   74        * Provide the given field into this field manager.
   75        */
   76       protected abstract void provideField(Object toAttach, StateManagerImpl sm,
   77           int field);
   78   
   79       /**
   80        * Return a PNew/PNewProvisional managed object for the given detached 
   81        * instance.
   82        */
   83       protected StateManagerImpl persist(AttachManager manager,
   84           PersistenceCapable pc, ClassMetaData meta, Object appId, 
   85           boolean explicit) {
   86           PersistenceCapable newInstance;
   87           if (!manager.getCopyNew())
   88               newInstance = pc;
   89           else if (appId == null)
   90               // datastore identity or application identity with generated keys
   91               newInstance = pc.pcNewInstance(null, false);
   92           else // application identity: use existing fields
   93               newInstance = pc.pcNewInstance(null, appId, false);
   94   
   95           return (StateManagerImpl) manager.getBroker().persist
   96               (newInstance, appId, explicit, manager.getBehavior());
   97       }
   98   
   99       /**
  100        * Attach the given field into the given instance.
  101        *
  102        * @param toAttach the detached persistent instance
  103        * @param sm state manager for the managed instance we're copying
  104        * into; <code>toAttach</code> also uses this state manager
  105        * @param fmd metadata on the field we're copying
  106        * @param nullLoaded if false, nulls will be considered unloaded and will
  107        * not be attached
  108        */
  109       protected boolean attachField(AttachManager manager, Object toAttach,
  110           StateManagerImpl sm, FieldMetaData fmd, boolean nullLoaded) {
  111           if (fmd.isVersion()
  112               || fmd.getManagement() != FieldMetaData.MANAGE_PERSISTENT)
  113               return false;
  114   
  115           PersistenceCapable into = sm.getPersistenceCapable();
  116           int i = fmd.getIndex();
  117           provideField(toAttach, sm, i);
  118   
  119           int set = StateManager.SET_ATTACH;
  120           Object val;
  121           switch (fmd.getDeclaredTypeCode()) {
  122               case JavaTypes.BOOLEAN:
  123                   sm.settingBooleanField(into, i, sm.fetchBooleanField(i),
  124                       fetchBooleanField(i), set);
  125                   break;
  126               case JavaTypes.BYTE:
  127                   sm.settingByteField(into, i, sm.fetchByteField(i),
  128                       fetchByteField(i), set);
  129                   break;
  130               case JavaTypes.CHAR:
  131                   sm.settingCharField(into, i, sm.fetchCharField(i),
  132                       fetchCharField(i), set);
  133                   break;
  134               case JavaTypes.DOUBLE:
  135                   sm.settingDoubleField(into, i, sm.fetchDoubleField(i),
  136                       fetchDoubleField(i), set);
  137                   break;
  138               case JavaTypes.FLOAT:
  139                   sm.settingFloatField(into, i, sm.fetchFloatField(i),
  140                       fetchFloatField(i), set);
  141                   break;
  142               case JavaTypes.INT:
  143                   sm.settingIntField(into, i, sm.fetchIntField(i),
  144                       fetchIntField(i), set);
  145                   break;
  146               case JavaTypes.LONG:
  147                   sm.settingLongField(into, i, sm.fetchLongField(i),
  148                       fetchLongField(i), set);
  149                   break;
  150               case JavaTypes.SHORT:
  151                   sm.settingShortField(into, i, sm.fetchShortField(i),
  152                       fetchShortField(i), set);
  153                   break;
  154               case JavaTypes.STRING:
  155                   String sval = fetchStringField(i);
  156                   if (sval == null && !nullLoaded)
  157                       return false;
  158                   sm.settingStringField(into, i, sm.fetchStringField(i), sval,
  159                       set);
  160                   break;
  161               case JavaTypes.DATE:
  162               case JavaTypes.CALENDAR:
  163               case JavaTypes.NUMBER:
  164               case JavaTypes.BOOLEAN_OBJ:
  165               case JavaTypes.BYTE_OBJ:
  166               case JavaTypes.CHAR_OBJ:
  167               case JavaTypes.DOUBLE_OBJ:
  168               case JavaTypes.FLOAT_OBJ:
  169               case JavaTypes.INT_OBJ:
  170               case JavaTypes.LONG_OBJ:
  171               case JavaTypes.SHORT_OBJ:
  172               case JavaTypes.BIGDECIMAL:
  173               case JavaTypes.BIGINTEGER:
  174               case JavaTypes.LOCALE:
  175               case JavaTypes.OBJECT:
  176               case JavaTypes.OID:
  177                   val = fetchObjectField(i);
  178                   if (val == null && !nullLoaded)
  179                       return false;
  180                   sm.settingObjectField(into, i, sm.fetchObjectField(i), val,
  181                       set);
  182                   break;
  183               case JavaTypes.PC:
  184               case JavaTypes.PC_UNTYPED:
  185                   Object frmpc = fetchObjectField(i);
  186                   if (frmpc == null && !nullLoaded)
  187                       return false;
  188                   OpenJPAStateManager tosm = manager.getBroker().getStateManager
  189                       (sm.fetchObjectField(i));
  190                   PersistenceCapable topc = (tosm == null) ? null
  191                       : tosm.getPersistenceCapable();
  192                   if (frmpc != null || topc != null) {
  193                       if (fmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE)
  194                           frmpc = getReference(manager, frmpc, sm, fmd);
  195                       else {
  196                           PersistenceCapable intopc = topc;
  197                           if (!fmd.isEmbeddedPC() && frmpc != null && topc != null
  198                               && !ObjectUtils.equals(topc.pcFetchObjectId(),
  199                               manager.getDetachedObjectId(frmpc))) {
  200                               intopc = null;
  201                           }
  202                           frmpc = manager.attach(frmpc, intopc, sm, fmd, false);
  203                       }
  204                       if (frmpc != topc)
  205                           sm.settingObjectField(into, i, topc, frmpc, set);
  206                   }
  207                   break;
  208               case JavaTypes.COLLECTION:
  209                   Collection frmc = (Collection) fetchObjectField(i);
  210                   if (frmc == null && !nullLoaded)
  211                       return false;
  212                   Collection toc = (Collection) sm.fetchObjectField(i);
  213                   if ((toc != null && !toc.isEmpty())
  214                       || frmc != null && !frmc.isEmpty()) {
  215                       if (frmc == null)
  216                           sm.settingObjectField(into, i, toc, null, set);
  217                       else if (toc == null) {
  218                           sm.settingObjectField(into, i, null,
  219                               attachCollection(manager, frmc, sm, fmd), set);
  220                       } else if (toc instanceof Set && frmc instanceof Set)
  221                           replaceCollection(manager, frmc, toc, sm, fmd);
  222                       else {
  223                           sm.settingObjectField(into, i, toc,
  224                               replaceList(manager, frmc, toc, sm, fmd), set);
  225                       }
  226                   }
  227                   break;
  228               case JavaTypes.MAP:
  229                   Map frmm = (Map) fetchObjectField(i);
  230                   if (frmm == null && !nullLoaded)
  231                       return false;
  232                   Map tom = (Map) sm.fetchObjectField(i);
  233                   if ((tom != null && !tom.isEmpty())
  234                       || (frmm != null && !frmm.isEmpty())) {
  235                       if (frmm == null)
  236                           sm.settingObjectField(into, i, tom, null, set);
  237                       else if (tom == null)
  238                           sm.settingObjectField(into, i, null,
  239                               attachMap(manager, frmm, sm, fmd), set);
  240                       else
  241                           replaceMap(manager, frmm, tom, sm, fmd);
  242                   }
  243                   break;
  244               case JavaTypes.ARRAY:
  245                   Object frma = fetchObjectField(i);
  246                   if (frma == null && !nullLoaded)
  247                       return false;
  248                   Object toa = sm.fetchObjectField(i);
  249                   if ((toa != null && Array.getLength(toa) > 0)
  250                       || (frma != null && Array.getLength(frma) > 0)) {
  251                       if (frma == null)
  252                           sm.settingObjectField(into, i, toa, null, set);
  253                       else
  254                           sm.settingObjectField(into, i, toa,
  255                               replaceArray(manager, frma, toa, sm, fmd), set);
  256                   }
  257                   break;
  258               default:
  259                   throw new InternalException(fmd.toString());
  260           }
  261           return true;
  262       }
  263   
  264       /**
  265        * Return a managed, possibly hollow reference for the given detached
  266        * object.
  267        */
  268       protected Object getReference(AttachManager manager, Object toAttach,
  269           OpenJPAStateManager sm, ValueMetaData vmd) {
  270           if (toAttach == null)
  271               return null;
  272   
  273           if (manager.getBroker().isNew(toAttach)
  274               || manager.getBroker().isPersistent(toAttach)) {
  275               return toAttach;
  276           } else if (manager.getBroker().isDetached(toAttach)) {
  277               Object oid = manager.getDetachedObjectId(toAttach);
  278               if (oid != null)
  279                   return manager.getBroker().find(oid, false, null);
  280           }
  281           throw new UserException(_loc.get("cant-cascade-attach", vmd))
  282               .setFailedObject(toAttach);
  283       }
  284   
  285       /**
  286        * Replace the contents of <code>toc</code> with the contents of
  287        * <code>frmc</code>. Neither collection is null.
  288        */
  289       private void replaceCollection(AttachManager manager, Collection frmc,
  290           Collection toc, OpenJPAStateManager sm, FieldMetaData fmd) {
  291           // if frmc collection is empty, just clear toc
  292           if (frmc.isEmpty()) {
  293               if (!toc.isEmpty())
  294                   toc.clear();
  295               return;
  296           }
  297   
  298           // if this is a pc collection, attach all instances
  299           boolean pc = fmd.getElement().isDeclaredTypePC();
  300           if (pc)
  301               frmc = attachCollection(manager, frmc, sm, fmd);
  302   
  303           // remove all elements from the toc collection that aren't in frmc
  304           toc.retainAll(frmc);
  305   
  306           // now add all elements that are in frmc but not toc
  307           if (frmc.size() != toc.size()) {
  308               for (Iterator i = frmc.iterator(); i.hasNext();) {
  309                   Object ob = i.next();
  310                   if (!toc.contains(ob))
  311                       toc.add(ob);
  312               }
  313           }
  314       }
  315   
  316       /**
  317        * Return a new collection with the attached contents of the given one.
  318        */
  319       protected Collection attachCollection(AttachManager manager,
  320           Collection orig, OpenJPAStateManager sm, FieldMetaData fmd) {
  321           Collection coll = copyCollection(manager, orig, fmd);
  322           ValueMetaData vmd = fmd.getElement();
  323           if (!vmd.isDeclaredTypePC())
  324               return coll;
  325   
  326           // unfortunately we have to clear the original and re-add
  327           coll.clear();
  328           Object elem;
  329           for (Iterator itr = orig.iterator(); itr.hasNext();) {
  330               if (vmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE)
  331                   elem = getReference(manager, itr.next(), sm, vmd);
  332               else
  333                   elem = manager.attach(itr.next(), null, sm, vmd, false);
  334               coll.add(elem);
  335           }
  336           return coll;
  337       }
  338   
  339       /**
  340        * Copies the given collection.
  341        */
  342       private Collection copyCollection(AttachManager manager, Collection orig,
  343           FieldMetaData fmd) {
  344           Collection coll = manager.getProxyManager().copyCollection(orig);
  345           if (coll == null)
  346               throw new UserException(_loc.get("not-copyable", fmd));
  347           return coll;
  348       }
  349   
  350       /**
  351        * Returns an attached version of the <code>frml</code>
  352        * list if it is different than <code>tol</code>. If the lists
  353        * will be identical, returns <code>tol</code>. Neither list is null.
  354        */
  355       private Collection replaceList(AttachManager manager, Collection frml,
  356           Collection tol, OpenJPAStateManager sm, FieldMetaData fmd) {
  357           boolean pc = fmd.getElement().isDeclaredTypePC();
  358           if (pc)
  359               frml = attachCollection(manager, frml, sm, fmd);
  360   
  361           // if the only diff between frml and tol is some added elements at
  362           // the end, make the changes directly in tol
  363           if (frml.size() >= tol.size()) {
  364               Iterator frmi = frml.iterator();
  365               for (Iterator toi = tol.iterator(); toi.hasNext();) {
  366                   // if there's an incompatibility, just return a copy of frml
  367                   // (it's already copied if we attached it)
  368                   if (!equals(frmi.next(), toi.next(), pc))
  369                       return (pc) ? frml : copyCollection(manager, frml, fmd);
  370               }
  371   
  372               // just add the extra elements in frml to tol and return tol
  373               while (frmi.hasNext())
  374                   tol.add(frmi.next());
  375               return tol;
  376           }
  377   
  378           // the lists are different; just make sure frml is copied and return it
  379           return (pc) ? frml : copyCollection(manager, frml, fmd);
  380       }
  381   
  382       /**
  383        * Replace the contents of <code>tom</code> with the contents of
  384        * <code>frmm</code>. Neither map is null.
  385        */
  386       private void replaceMap(AttachManager manager, Map frmm, Map tom,
  387           OpenJPAStateManager sm, FieldMetaData fmd) {
  388           if (frmm.isEmpty()) {
  389               if (!tom.isEmpty())
  390                   tom.clear();
  391               return;
  392           }
  393   
  394           // if this is a pc map, attach all instances
  395           boolean keyPC = fmd.getKey().isDeclaredTypePC();
  396           boolean valPC = fmd.getElement().isDeclaredTypePC();
  397           if (keyPC || valPC)
  398               frmm = attachMap(manager, frmm, sm, fmd);
  399   
  400           // make sure all the keys in the from map are in the two map, and
  401           // that they have the same values
  402           for (Iterator i = frmm.entrySet().iterator(); i.hasNext();) {
  403               Map.Entry entry = (Map.Entry) i.next();
  404               if (!tom.containsKey(entry.getKey())
  405                   || !equals(tom.get(entry.getKey()), entry.getValue(), valPC)) {
  406                   tom.put(entry.getKey(), entry.getValue());
  407               }
  408           }
  409   
  410           // remove any keys in the to map that aren't in the from map
  411           if (tom.size() != frmm.size()) {
  412               for (Iterator i = tom.keySet().iterator(); i.hasNext();) {
  413                   if (!(frmm.containsKey(i.next())))
  414                       i.remove();
  415               }
  416           }
  417       }
  418   
  419       /**
  420        * Make sure all the values in the given map are attached.
  421        */
  422       protected Map attachMap(AttachManager manager, Map orig,
  423           OpenJPAStateManager sm, FieldMetaData fmd) {
  424           Map map = manager.getProxyManager().copyMap(orig);
  425           if (map == null)
  426               throw new UserException(_loc.get("not-copyable", fmd));
  427   
  428           ValueMetaData keymd = fmd.getKey();
  429           ValueMetaData valmd = fmd.getElement();
  430           if (!keymd.isDeclaredTypePC() && !valmd.isDeclaredTypePC())
  431               return map;
  432   
  433           // if we have to replace keys, just clear and re-add; otherwise
  434           // we can use the entry set to reset the values only
  435           Map.Entry entry;
  436           if (keymd.isDeclaredTypePC()) {
  437               map.clear();
  438               Object key, val;
  439               for (Iterator itr = orig.entrySet().iterator(); itr.hasNext();) {
  440                   entry = (Map.Entry) itr.next();
  441                   key = entry.getKey();
  442                   if (keymd.getCascadeAttach() == ValueMetaData.CASCADE_NONE)
  443                       key = getReference(manager, key, sm, keymd);
  444                   else
  445                       key = manager.attach(key, null, sm, keymd, false);
  446                   val = entry.getValue();
  447                   if (valmd.isDeclaredTypePC()) {
  448                       if (valmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE)
  449                           val = getReference(manager, val, sm, valmd);
  450                       else
  451                           val = manager.attach(val, null, sm, valmd, false);
  452                   }
  453                   map.put(key, val);
  454               }
  455           } else {
  456               Object val;
  457               for (Iterator itr = map.entrySet().iterator(); itr.hasNext();) {
  458                   entry = (Map.Entry) itr.next();
  459                   if (valmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE)
  460                       val = getReference(manager, entry.getValue(), sm, valmd);
  461                   else
  462                       val = manager.attach(entry.getValue(), null, sm, valmd, 
  463                           false);
  464                   entry.setValue(val);
  465               }
  466           }
  467           return map;
  468       }
  469   
  470       /**
  471        * Returns an attached version of the <code>frma</code>
  472        * array if it is different than <code>toa</code>. If the arrays
  473        * will be identical, returns <code>toa</code>.
  474        */
  475       private Object replaceArray(AttachManager manager, Object frma,
  476           Object toa, OpenJPAStateManager sm, FieldMetaData fmd) {
  477           int len = Array.getLength(frma);
  478           boolean diff = toa == null || len != Array.getLength(toa);
  479   
  480           // populate an array copy on the initial assumption that the array
  481           // is dirty
  482           Object newa = Array.newInstance(fmd.getElement().getDeclaredType(),
  483               len);
  484           ValueMetaData vmd = fmd.getElement();
  485           boolean pc = vmd.isDeclaredTypePC();
  486           Object elem;
  487           for (int i = 0; i < len; i++) {
  488               elem = Array.get(frma, i);
  489               if (pc) {
  490                   if (vmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE)
  491                       elem = getReference(manager, elem, sm, vmd);
  492                   else
  493                       elem = manager.attach(elem, null, sm, vmd, false);
  494               }
  495               diff = diff || !equals(elem, Array.get(toa, i), pc);
  496               Array.set(newa, i, elem);
  497           }
  498           return (diff) ? newa : toa;
  499       }
  500   
  501       /**
  502        * Return true if the given objects are equal. PCs are compared for
  503        * on JVM identity.
  504        */
  505       private static boolean equals(Object a, Object b, boolean pc) {
  506           if (a == b)
  507               return true;
  508           if (pc || a == null || b == null)
  509               return false;
  510   		return a.equals (b);
  511   	}
  512   }

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