blob: 6b3e657e777bb4ecab7b414ab417d4b3fcc92b14 [file] [log] [blame]
/*
* 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);
}
}