| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.openjpa.kernel; |
| |
| import java.lang.reflect.Array; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| import org.apache.openjpa.enhance.PersistenceCapable; |
| import org.apache.openjpa.enhance.StateManager; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.meta.ClassMetaData; |
| import org.apache.openjpa.meta.FieldMetaData; |
| import org.apache.openjpa.meta.JavaTypes; |
| import org.apache.openjpa.meta.ValueMetaData; |
| import org.apache.openjpa.util.InternalException; |
| import org.apache.openjpa.util.UserException; |
| |
| /** |
| * Strategy for attaching objects. |
| * |
| * @author Marc Prud'hommeaux |
| * @author Steve Kim |
| */ |
| abstract class AttachStrategy |
| extends TransferFieldManager { |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (AttachStrategy.class); |
| |
| /** |
| * Attach. |
| * |
| * @param manager manager holding cache of attached instances |
| * @param toAttach detached instance |
| * @param meta metadata for the instance being attached |
| * @param into instance we're attaching into |
| * @param owner state manager for <code>into</code> |
| * @param ownerMeta field we traversed to find <code>toAttach</code> |
| * @param explicit whether to make new instances explicitly persistent |
| */ |
| public abstract Object attach(AttachManager manager, |
| Object toAttach, ClassMetaData meta, PersistenceCapable into, |
| OpenJPAStateManager owner, ValueMetaData ownerMeta, boolean explicit); |
| |
| /** |
| * Return the identity of the given detached instance. |
| */ |
| protected abstract Object getDetachedObjectId(AttachManager manager, |
| Object toAttach); |
| |
| /** |
| * Provide the given field into this field manager. |
| */ |
| protected abstract void provideField(Object toAttach, StateManagerImpl sm, |
| int field); |
| |
| /** |
| * Return a PNew/PNewProvisional managed object for the given detached |
| * instance. |
| */ |
| protected StateManagerImpl persist(AttachManager manager, |
| PersistenceCapable pc, ClassMetaData meta, Object appId, |
| boolean explicit) { |
| PersistenceCapable newInstance; |
| if (!manager.getCopyNew()) |
| newInstance = pc; |
| else if (appId == null) |
| // datastore identity or application identity with generated keys |
| newInstance = pc.pcNewInstance(null, false); |
| else // application identity: use existing fields |
| newInstance = pc.pcNewInstance(null, appId, false); |
| |
| StateManagerImpl sm = (StateManagerImpl) manager.getBroker().persist |
| (newInstance, appId, explicit, manager.getBehavior(), !manager.getCopyNew()); |
| |
| attachPCKeyFields(pc, sm, meta, manager); |
| |
| return sm; |
| } |
| |
| private void attachPCKeyFields(PersistenceCapable fromPC, |
| StateManagerImpl sm, ClassMetaData meta, AttachManager manager) { |
| |
| |
| if (fromPC.pcGetStateManager() == null) { |
| fromPC.pcReplaceStateManager(sm); |
| |
| FieldMetaData[] fmds = meta.getDefinedFields(); |
| for (FieldMetaData fmd : fmds) { |
| if (fmd.isPrimaryKey() && fmd.getDeclaredTypeCode() == JavaTypes.PC) { |
| attachField(manager, fromPC, sm, fmd, true); |
| } |
| } |
| |
| fromPC.pcReplaceStateManager(null); |
| } |
| } |
| |
| /** |
| * Attach the given field into the given instance. |
| * |
| * @param toAttach the detached persistent instance |
| * @param sm state manager for the managed instance we're copying |
| * into; <code>toAttach</code> also uses this state manager |
| * @param fmd metadata on the field we're copying |
| * @param nullLoaded if false, nulls will be considered unloaded and will |
| * not be attached |
| */ |
| protected boolean attachField(AttachManager manager, Object toAttach, |
| StateManagerImpl sm, FieldMetaData fmd, boolean nullLoaded) { |
| if (fmd.isVersion() |
| || fmd.getManagement() != FieldMetaData.MANAGE_PERSISTENT) |
| return false; |
| |
| PersistenceCapable into = sm.getPersistenceCapable(); |
| int i = fmd.getIndex(); |
| provideField(toAttach, sm, i); |
| |
| int set = StateManager.SET_ATTACH; |
| Object val; |
| switch (fmd.getDeclaredTypeCode()) { |
| case JavaTypes.BOOLEAN: |
| sm.settingBooleanField(into, i, sm.fetchBooleanField(i), |
| fetchBooleanField(i), set); |
| break; |
| case JavaTypes.BYTE: |
| sm.settingByteField(into, i, sm.fetchByteField(i), |
| fetchByteField(i), set); |
| break; |
| case JavaTypes.CHAR: |
| sm.settingCharField(into, i, sm.fetchCharField(i), |
| fetchCharField(i), set); |
| break; |
| case JavaTypes.DOUBLE: |
| sm.settingDoubleField(into, i, sm.fetchDoubleField(i), |
| fetchDoubleField(i), set); |
| break; |
| case JavaTypes.FLOAT: |
| sm.settingFloatField(into, i, sm.fetchFloatField(i), |
| fetchFloatField(i), set); |
| break; |
| case JavaTypes.INT: |
| sm.settingIntField(into, i, sm.fetchIntField(i), |
| fetchIntField(i), set); |
| break; |
| case JavaTypes.LONG: |
| sm.settingLongField(into, i, sm.fetchLongField(i), |
| fetchLongField(i), set); |
| break; |
| case JavaTypes.SHORT: |
| sm.settingShortField(into, i, sm.fetchShortField(i), |
| fetchShortField(i), set); |
| break; |
| case JavaTypes.STRING: |
| String sval = fetchStringField(i); |
| if (sval == null && !nullLoaded) |
| return false; |
| sm.settingStringField(into, i, sm.fetchStringField(i), sval, |
| set); |
| break; |
| case JavaTypes.DATE: |
| case JavaTypes.CALENDAR: |
| case JavaTypes.LOCAL_DATE: |
| case JavaTypes.LOCAL_TIME: |
| case JavaTypes.LOCAL_DATETIME: |
| case JavaTypes.OFFSET_TIME: |
| case JavaTypes.OFFSET_DATETIME: |
| case JavaTypes.NUMBER: |
| case JavaTypes.BOOLEAN_OBJ: |
| case JavaTypes.BYTE_OBJ: |
| case JavaTypes.CHAR_OBJ: |
| case JavaTypes.DOUBLE_OBJ: |
| case JavaTypes.FLOAT_OBJ: |
| case JavaTypes.INT_OBJ: |
| case JavaTypes.LONG_OBJ: |
| case JavaTypes.SHORT_OBJ: |
| case JavaTypes.BIGDECIMAL: |
| case JavaTypes.BIGINTEGER: |
| case JavaTypes.LOCALE: |
| case JavaTypes.OBJECT: |
| case JavaTypes.OID: |
| case JavaTypes.ENUM: |
| val = fetchObjectField(i); |
| if (val == null && !nullLoaded) |
| return false; |
| sm.settingObjectField(into, i, sm.fetchObjectField(i), val, |
| set); |
| break; |
| case JavaTypes.PC: |
| case JavaTypes.PC_UNTYPED: |
| Object frmpc = fetchObjectField(i); |
| if (frmpc == null && !nullLoaded) |
| return false; |
| |
| OpenJPAStateManager tosm = manager.getBroker().getStateManager |
| (sm.fetchObjectField(i)); |
| PersistenceCapable topc = (tosm == null) ? null |
| : tosm.getPersistenceCapable(); |
| if (frmpc != null || topc != null) { |
| if (fmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE) { |
| // Use the attached copy of the object, if available |
| PersistenceCapable cpy = manager.getAttachedCopy(frmpc); |
| if (cpy != null) { |
| frmpc = cpy; |
| } else { |
| frmpc = getReference(manager, frmpc, sm, fmd); |
| } |
| } |
| else { |
| PersistenceCapable intopc = topc; |
| if (!fmd.isEmbeddedPC() && frmpc != null && topc != null |
| && !Objects.equals(topc.pcFetchObjectId(), |
| manager.getDetachedObjectId(frmpc))) { |
| intopc = null; |
| } |
| frmpc = manager.attach(frmpc, intopc, sm, fmd, false); |
| } |
| if (frmpc != topc) |
| sm.settingObjectField(into, i, topc, frmpc, set); |
| } |
| break; |
| case JavaTypes.COLLECTION: |
| Collection frmc = (Collection) fetchObjectField(i); |
| if (frmc == null && !nullLoaded) |
| return false; |
| Collection toc = (Collection) sm.fetchObjectField(i); |
| if ((toc != null && !toc.isEmpty()) |
| || frmc != null && !frmc.isEmpty()) { |
| if (frmc == null) |
| sm.settingObjectField(into, i, toc, null, set); |
| else if (toc == null) { |
| sm.settingObjectField(into, i, null, |
| attachCollection(manager, frmc, sm, fmd), set); |
| } else if (toc instanceof Set && frmc instanceof Set) |
| replaceCollection(manager, frmc, toc, sm, fmd); |
| else { |
| sm.settingObjectField(into, i, toc, |
| replaceList(manager, frmc, toc, sm, fmd), set); |
| } |
| } |
| break; |
| case JavaTypes.MAP: |
| Map frmm = (Map) fetchObjectField(i); |
| if (frmm == null && !nullLoaded) |
| return false; |
| Map tom = (Map) sm.fetchObjectField(i); |
| if ((tom != null && !tom.isEmpty()) |
| || (frmm != null && !frmm.isEmpty())) { |
| if (frmm == null) |
| sm.settingObjectField(into, i, tom, null, set); |
| else if (tom == null) |
| sm.settingObjectField(into, i, null, |
| attachMap(manager, frmm, sm, fmd), set); |
| else |
| replaceMap(manager, frmm, tom, sm, fmd); |
| } |
| break; |
| case JavaTypes.ARRAY: |
| Object frma = fetchObjectField(i); |
| if (frma == null && !nullLoaded) |
| return false; |
| Object toa = sm.fetchObjectField(i); |
| if ((toa != null && Array.getLength(toa) > 0) |
| || (frma != null && Array.getLength(frma) > 0)) { |
| if (frma == null) |
| sm.settingObjectField(into, i, toa, null, set); |
| else |
| sm.settingObjectField(into, i, toa, |
| replaceArray(manager, frma, toa, sm, fmd), set); |
| } |
| break; |
| default: |
| throw new InternalException(fmd.toString()); |
| } |
| return true; |
| } |
| |
| /** |
| * Return a managed, possibly hollow reference for the given detached |
| * object. |
| */ |
| protected Object getReference(AttachManager manager, Object toAttach, OpenJPAStateManager sm, ValueMetaData vmd) { |
| if (toAttach == null) |
| return null; |
| |
| if (manager.getBroker().isNew(toAttach)) { |
| // Check if toAttach is already mapped to a managed instance |
| PersistenceCapable pc = manager.getAttachedCopy(toAttach); |
| if (pc != null) { |
| return pc; |
| } else { |
| return toAttach; |
| } |
| } else if (manager.getBroker().isPersistent(toAttach)) { |
| return toAttach; |
| } else if (manager.getBroker().isDetached(toAttach)) { |
| Object oid = manager.getDetachedObjectId(toAttach); |
| if (oid != null) { |
| return manager.getBroker().find(oid, false, null); |
| } |
| } |
| throw new UserException(_loc.get("cant-cascade-attach", vmd)).setFailedObject(toAttach); |
| } |
| |
| /** |
| * Replace the contents of <code>toc</code> with the contents of |
| * <code>frmc</code>. Neither collection is null. |
| */ |
| private void replaceCollection(AttachManager manager, Collection frmc, |
| Collection toc, OpenJPAStateManager sm, FieldMetaData fmd) { |
| // if frmc collection is empty, just clear toc |
| if (frmc.isEmpty()) { |
| if (!toc.isEmpty()) |
| toc.clear(); |
| return; |
| } |
| |
| // if this is a pc collection, attach all instances |
| boolean pc = fmd.getElement().isDeclaredTypePC(); |
| if (pc) |
| frmc = attachCollection(manager, frmc, sm, fmd); |
| |
| // remove all elements from the toc collection that aren't in frmc |
| toc.retainAll(frmc); |
| |
| // now add all elements that are in frmc but not toc |
| if (frmc.size() != toc.size()) { |
| for (Object ob : frmc) { |
| if (!toc.contains(ob)) |
| toc.add(ob); |
| } |
| } |
| } |
| |
| /** |
| * Return a new collection with the attached contents of the given one. |
| */ |
| protected Collection attachCollection(AttachManager manager, |
| Collection orig, OpenJPAStateManager sm, FieldMetaData fmd) { |
| Collection coll = copyCollection(manager, orig, fmd, sm); |
| ValueMetaData vmd = fmd.getElement(); |
| if (!vmd.isDeclaredTypePC()) |
| return coll; |
| |
| // unfortunately we have to clear the original and re-add |
| coll.clear(); |
| Object elem; |
| for (Iterator itr = orig.iterator(); itr.hasNext();) { |
| if (vmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE) |
| elem = getReference(manager, itr.next(), sm, vmd); |
| else |
| elem = manager.attach(itr.next(), null, sm, vmd, false); |
| coll.add(elem); |
| } |
| return coll; |
| } |
| |
| /** |
| * Copies the given collection. |
| */ |
| private Collection copyCollection(AttachManager manager, Collection orig, |
| FieldMetaData fmd) { |
| Collection coll = manager.getProxyManager().copyCollection(orig); |
| if (coll == null) |
| throw new UserException(_loc.get("not-copyable", fmd)); |
| return coll; |
| } |
| |
| /** |
| * Copies the given collection. |
| */ |
| private Collection copyCollection(AttachManager manager, Collection orig, |
| FieldMetaData fmd, OpenJPAStateManager sm) { |
| if (orig == null) |
| throw new UserException(_loc.get("not-copyable", fmd)); |
| try { |
| return copyCollection(manager, orig, fmd); |
| } catch (Exception e) { |
| Collection coll = (Collection) sm.newFieldProxy(fmd.getIndex()); |
| coll.addAll(orig); |
| return coll; |
| } |
| } |
| |
| /** |
| * Copies the given map. |
| */ |
| private Map copyMap(AttachManager manager, Map orig, |
| FieldMetaData fmd, OpenJPAStateManager sm) { |
| if (orig == null) |
| throw new UserException(_loc.get("not-copyable", fmd)); |
| try { |
| return manager.getProxyManager().copyMap(orig); |
| } catch (Exception e) { |
| Map<Object, Object> map = (Map<Object, Object>) sm.newFieldProxy(fmd.getIndex()); |
| |
| for (Entry<Object, Object> entry : ((Map<Object, Object>) orig).entrySet()) { |
| map.put(entry.getKey(), entry.getValue()); |
| } |
| return map; |
| } |
| } |
| |
| /** |
| * Returns an attached version of the <code>frml</code> |
| * list if it is different than <code>tol</code>. If the lists |
| * will be identical, returns <code>tol</code>. Neither list is null. |
| */ |
| private Collection replaceList(AttachManager manager, Collection frml, |
| Collection tol, OpenJPAStateManager sm, FieldMetaData fmd) { |
| boolean pc = fmd.getElement().isDeclaredTypePC(); |
| if (pc) |
| frml = attachCollection(manager, frml, sm, fmd); |
| |
| // if the only diff between frml and tol is some added elements at |
| // the end, make the changes directly in tol |
| if (frml.size() >= tol.size()) { |
| Iterator frmi = frml.iterator(); |
| for (Object o : tol) { |
| // if there's an incompatibility, just return a copy of frml |
| // (it's already copied if we attached it) |
| if (!equals(frmi.next(), o, pc)) |
| return (pc) ? frml : copyCollection(manager, frml, fmd, sm); |
| } |
| |
| // just add the extra elements in frml to tol and return tol |
| while (frmi.hasNext()) |
| tol.add(frmi.next()); |
| return tol; |
| } |
| |
| // the lists are different; just make sure frml is copied and return it |
| return (pc) ? frml : copyCollection(manager, frml, fmd, sm); |
| } |
| |
| /** |
| * Replace the contents of <code>tom</code> with the contents of |
| * <code>frmm</code>. Neither map is null. |
| */ |
| private void replaceMap(AttachManager manager, Map frmm, Map tom, |
| OpenJPAStateManager sm, FieldMetaData fmd) { |
| if (frmm.isEmpty()) { |
| if (!tom.isEmpty()) |
| tom.clear(); |
| return; |
| } |
| |
| // if this is a pc map, attach all instances |
| boolean keyPC = fmd.getKey().isDeclaredTypePC(); |
| boolean valPC = fmd.getElement().isDeclaredTypePC(); |
| if (keyPC || valPC) |
| frmm = attachMap(manager, frmm, sm, fmd); |
| |
| // make sure all the keys in the from map are in the two map, and |
| // that they have the same values |
| for (Object o : frmm.entrySet()) { |
| Entry entry = (Entry) o; |
| if (!tom.containsKey(entry.getKey()) |
| || !equals(tom.get(entry.getKey()), entry.getValue(), valPC)) { |
| tom.put(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| // remove any keys in the to map that aren't in the from map |
| if (tom.size() != frmm.size()) { |
| for (Iterator i = tom.keySet().iterator(); i.hasNext();) { |
| if (!(frmm.containsKey(i.next()))) |
| i.remove(); |
| } |
| } |
| } |
| |
| /** |
| * Make sure all the values in the given map are attached. |
| */ |
| protected Map attachMap(AttachManager manager, Map orig, |
| OpenJPAStateManager sm, FieldMetaData fmd) { |
| Map map = copyMap(manager, orig, fmd, sm); |
| if (map == null) |
| throw new UserException(_loc.get("not-copyable", fmd)); |
| |
| ValueMetaData keymd = fmd.getKey(); |
| ValueMetaData valmd = fmd.getElement(); |
| if (!keymd.isDeclaredTypePC() && !valmd.isDeclaredTypePC()) |
| return map; |
| |
| // if we have to replace keys, just clear and re-add; otherwise |
| // we can use the entry set to reset the values only |
| Map.Entry entry; |
| if (keymd.isDeclaredTypePC()) { |
| map.clear(); |
| Object key, val; |
| for (Object o : orig.entrySet()) { |
| entry = (Entry) o; |
| key = entry.getKey(); |
| if (keymd.getCascadeAttach() == ValueMetaData.CASCADE_NONE) |
| key = getReference(manager, key, sm, keymd); |
| else |
| key = manager.attach(key, null, sm, keymd, false); |
| val = entry.getValue(); |
| if (valmd.isDeclaredTypePC()) { |
| if (valmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE) |
| val = getReference(manager, val, sm, valmd); |
| else |
| val = manager.attach(val, null, sm, valmd, false); |
| } |
| map.put(key, val); |
| } |
| } else { |
| Object val; |
| for (Object o : map.entrySet()) { |
| entry = (Entry) o; |
| if (valmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE) |
| val = getReference(manager, entry.getValue(), sm, valmd); |
| else |
| val = manager.attach(entry.getValue(), null, sm, valmd, |
| false); |
| entry.setValue(val); |
| } |
| } |
| return map; |
| } |
| |
| /** |
| * Returns an attached version of the <code>frma</code> |
| * array if it is different than <code>toa</code>. If the arrays |
| * will be identical, returns <code>toa</code>. |
| */ |
| private Object replaceArray(AttachManager manager, Object frma, |
| Object toa, OpenJPAStateManager sm, FieldMetaData fmd) { |
| int len = Array.getLength(frma); |
| boolean diff = toa == null || len != Array.getLength(toa); |
| |
| // populate an array copy on the initial assumption that the array |
| // is dirty |
| Object newa = Array.newInstance(fmd.getElement().getDeclaredType(), |
| len); |
| ValueMetaData vmd = fmd.getElement(); |
| boolean pc = vmd.isDeclaredTypePC(); |
| Object elem; |
| for (int i = 0; i < len; i++) { |
| elem = Array.get(frma, i); |
| if (pc) { |
| if (vmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE) |
| elem = getReference(manager, elem, sm, vmd); |
| else |
| elem = manager.attach(elem, null, sm, vmd, false); |
| } |
| diff = diff || !equals(elem, Array.get(toa, i), pc); |
| Array.set(newa, i, elem); |
| } |
| return (diff) ? newa : toa; |
| } |
| |
| /** |
| * Return true if the given objects are equal. PCs are compared for |
| * on JVM identity. |
| */ |
| private static boolean equals(Object a, Object b, boolean pc) { |
| if (a == b) |
| return true; |
| if (pc || a == null || b == null) |
| return false; |
| return a.equals (b); |
| } |
| } |