blob: 5f2d51ba1634bece687534b5ab272160b88e01db [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.util.BitSet;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.ValueMetaData;
import org.apache.openjpa.util.ApplicationIds;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.OptimisticException;
/**
* Handles attaching instances with detached state.
*
* @author Marc Prud'hommeaux
*/
class DetachedStateAttachStrategy
extends AttachStrategy {
private static final Localizer _loc = Localizer.forPackage
(DetachedStateAttachStrategy.class);
@Override
protected Object getDetachedObjectId(AttachManager manager,
Object toAttach) {
if (toAttach == null)
return null;
Broker broker = manager.getBroker();
PersistenceCapable pc = ImplHelper.toPersistenceCapable(toAttach,
broker.getConfiguration());
ClassMetaData meta = broker.getConfiguration().
getMetaDataRepositoryInstance().getMetaData(
ImplHelper.getManagedInstance(toAttach).getClass(),
broker.getClassLoader(), true);
switch (meta.getIdentityType()) {
case ClassMetaData.ID_DATASTORE:
Object[] state = (Object[]) pc.pcGetDetachedState();
if (state == null)
return null;
return broker
.newObjectId(toAttach.getClass(), state[0]);
case ClassMetaData.ID_APPLICATION:
return ApplicationIds.create(pc, meta);
default:
throw new InternalException();
}
}
@Override
protected void provideField(Object toAttach, StateManagerImpl sm,
int field) {
sm.provideField(ImplHelper.toPersistenceCapable(toAttach,
sm.getContext().getConfiguration()), this, field);
}
@Override
public Object attach(AttachManager manager, Object toAttach,
ClassMetaData meta, PersistenceCapable into, OpenJPAStateManager owner,
ValueMetaData ownerMeta, boolean explicit) {
BrokerImpl broker = manager.getBroker();
PersistenceCapable pc = ImplHelper.toPersistenceCapable(toAttach,
manager.getBroker().getConfiguration());
Object[] state = (Object[]) pc.pcGetDetachedState();
boolean embedded = ownerMeta != null && ownerMeta.isEmbeddedPC();
int offset;
StateManagerImpl sm;
// state == null means this is a new instance; also, if the
// state manager for the embedded instance is null, then
// it should be treated as a new instance (since the
// newly persisted owner may create a new embedded instance
// in the constructor); fixed bug #1075.
// also, if the user has attached a detached obj from somewhere
// else in the graph to an embedded field that was previously null,
// copy into a new embedded instance
if (embedded && (state == null || into == null
|| broker.getStateManager(into) == null)) {
if (into == null)
into = pc.pcNewInstance(null, false);
sm = (StateManagerImpl) broker.embed(into, null, owner, ownerMeta);
into = sm.getPersistenceCapable();
} else if (state == null) {
sm = persist(manager, pc, meta, ApplicationIds.create(pc, meta),
explicit);
into = sm.getPersistenceCapable();
} else if (!embedded && into == null) {
Object id = getDetachedObjectId(manager, pc);
if (id != null)
into =
ImplHelper.toPersistenceCapable(broker.find(id, true, null),
manager.getBroker().getConfiguration());
if (into == null) {
// we mark objects that were new on detach by putting an empty
// extra element in their detached state array
offset = meta.getIdentityType() == ClassMetaData.ID_DATASTORE ? 1 : 0;
boolean isNew = state.length == 3 + offset;
// attempting to attach an instance that has been deleted
// will throw an OVE if it was not PNEW when it was detached
if (!isNew)
throw new OptimisticException(_loc.get("attach-deleted",
ImplHelper.getManagedInstance(pc).getClass(), id))
.setFailedObject(id);
// if the instance does not exist, we assume that it was
// made persistent in a new transaction, detached, and then
// the transaction was rolled back; the danger is that
// the instance was made persistent, detached, committed,
// and then deleted, but this is an uncommon case
sm = persist(manager, pc, meta, id, explicit);
into = sm.getPersistenceCapable();
// nullify the state, since the new instance won't have one
state = null;
} else
sm = manager.assertManaged(into);
} else
sm = manager.assertManaged(into);
// mark that we attached the instance *before* we
// fill in values to avoid endless recursion
manager.setAttachedCopy(pc, into);
meta = sm.getMetaData();
manager.fireBeforeAttach(pc, meta);
offset = meta.getIdentityType() == ClassMetaData.ID_DATASTORE ? 1 : 0;
// assign the detached pc the same state manager as the object we're
// copying into during the attach process
pc.pcReplaceStateManager(sm);
BitSet fields = state == null ? null : (BitSet) state[1 + offset];
try {
FieldMetaData[] fmds = meta.getFields();
for (int i = 0; i < fmds.length; i++) {
// only attach fields in the FG of the detached instance; new
// instances get all their fields attached
if (fields == null || fields.get(i))
attachField(manager, pc, sm, fmds[i], true);
}
}
finally {
pc.pcReplaceStateManager(null);
}
// set the next version for non-new instances that are not embedded
if (state != null && !embedded) {
// make sure that all the fields in the original FG are loaded
// before we try to compare version
if (fields != null && !fields.equals(sm.getLoaded())) {
BitSet toLoad = (BitSet) fields.clone();
toLoad.andNot(sm.getLoaded()); // skip already loaded fields
if (toLoad.length() > 0)
sm.loadFields(toLoad, null, LockLevels.LOCK_NONE, null);
//### we should calculate lock level above
}
Object version = state[offset];
StoreManager store = broker.getStoreManager();
switch (store.compareVersion(sm, version, sm.getVersion())) {
case StoreManager.VERSION_LATER:
// we have a later version: set it into the object.
// lock validation will occur at commit time
sm.setVersion(version);
break;
case StoreManager.VERSION_EARLIER:
case StoreManager.VERSION_DIFFERENT:
sm.setVersion(version);
throw new OptimisticException(into);
case StoreManager.VERSION_SAME:
// no action required
break;
}
}
return ImplHelper.getManagedInstance(into);
}
}