blob: ae332d28c0b65653ae2c8cb3f81168e5575e6b51 [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.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.event.CallbackModes;
import org.apache.openjpa.event.LifecycleEvent;
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.CallbackException;
import org.apache.openjpa.util.Exceptions;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.OpenJPAException;
import org.apache.openjpa.util.OptimisticException;
import org.apache.openjpa.util.ProxyManager;
import org.apache.openjpa.util.UserException;
/**
* Handles attaching instances.
*
* @author Marc Prud'hommeaux
*/
public class AttachManager {
private static final Localizer _loc = Localizer.forPackage
(AttachManager.class);
private final BrokerImpl _broker;
private final ProxyManager _proxy;
private final OpCallbacks _call;
private final boolean _copyNew;
private final boolean _failFast;
private final IdentityHashMap _attached = new IdentityHashMap();
private final Collection<StateManagerImpl> _visitedNodes = new ArrayList();
// reusable strategies
private AttachStrategy _version;
private AttachStrategy _detach;
/**
* Constructor. Supply broker attaching to.
*/
public AttachManager(BrokerImpl broker, boolean copyNew, OpCallbacks call) {
_broker = broker;
_proxy = broker.getConfiguration().getProxyManagerInstance();
_call = call;
_copyNew = copyNew;
_failFast = (broker.getConfiguration().getMetaDataRepositoryInstance().
getMetaDataFactory().getDefaults().getCallbackMode()
& CallbackModes.CALLBACK_FAIL_FAST) != 0;
}
/**
* Return the behavior supplied on construction.
*/
public OpCallbacks getBehavior() {
return _call;
}
/**
* Return whether to copy new instances being persisted.
*/
public boolean getCopyNew() {
return _copyNew;
}
/**
* Return an attached version of the given instance.
*/
public Object attach(Object pc) {
if (pc == null)
return null;
CallbackException excep = null;
try {
return attach(pc, null, null, null, true);
} catch (CallbackException ce) {
excep = ce;
return null; // won't be reached as the exceps will be rethrown
} finally {
List exceps = null;
if (excep == null || !_failFast)
exceps = invokeAfterAttach(null);
else
exceps = Collections.singletonList(excep);
_attached.clear();
throwExceptions(exceps, null, false);
}
}
/**
* Return attached versions of the given instances.
*/
public Object[] attachAll(Collection instances) {
Object[] attached = new Object[instances.size()];
List exceps = null;
List failed = null;
boolean opt = true;
boolean failFast = false;
try {
int i = 0;
for (Iterator itr = instances.iterator(); itr.hasNext(); i++) {
try {
attached[i] = attach(itr.next(), null, null, null, true);
} catch (OpenJPAException ke) {
// track exceptions and optimistic failed objects
if (opt && !(ke instanceof OptimisticException))
opt = false;
if (opt && ke.getFailedObject() != null)
failed = add(failed, ke.getFailedObject());
exceps = add(exceps, ke);
if (ke instanceof CallbackException && _failFast) {
failFast = true;
break;
}
}
catch (RuntimeException re) {
exceps = add(exceps, re);
}
}
} finally {
// invoke post callbacks unless all failed
if (!failFast && (exceps == null
|| exceps.size() < instances.size()))
exceps = invokeAfterAttach(exceps);
_attached.clear();
}
throwExceptions(exceps, failed, opt);
return attached;
}
/**
* Invoke postAttach() on any attached instances that implement
* PostAttachCallback. This will be done after the entire graph has
* been attached.
*/
private List invokeAfterAttach(List exceps) {
Set entries = _attached.entrySet();
for (Object o : entries) {
Map.Entry entry = (Map.Entry) o;
Object attached = entry.getValue();
StateManagerImpl sm = _broker.getStateManagerImpl(attached, true);
if (sm.isNew())
continue;
try {
_broker.fireLifecycleEvent(attached, entry.getKey(),
sm.getMetaData(), LifecycleEvent.AFTER_ATTACH);
}
catch (RuntimeException re) {
exceps = add(exceps, re);
if (_failFast && re instanceof CallbackException)
break;
}
}
return exceps;
}
/**
* Add an object to the list.
*/
private List add(List list, Object obj) {
if (list == null)
list = new LinkedList();
list.add(obj);
return list;
}
/**
* Throw exception for failures.
*/
private void throwExceptions(List exceps, List failed, boolean opt) {
if (exceps == null)
return;
if (exceps.size() == 1)
throw (RuntimeException) exceps.get(0);
Throwable[] t = (Throwable[]) exceps.toArray
(new Throwable[exceps.size()]);
if (opt && failed != null)
throw new OptimisticException(failed, t);
if (opt)
throw new OptimisticException(t);
throw new UserException(_loc.get("nested-exceps")).
setNestedThrowables(t);
}
/**
* Attach.
*
* @param toAttach the detached object
* @param into the instance we're attaching into
* @param owner state manager for <code>into</code>
* @param ownerMeta the field we traversed to find <code>toAttach</code>
* @param explicit whether to make new instances explicitly persistent
*/
Object attach(Object toAttach, PersistenceCapable into,
OpenJPAStateManager owner, ValueMetaData ownerMeta, boolean explicit) {
if (toAttach == null)
return null;
// check if already attached
Object attached = _attached.get(toAttach);
if (attached != null)
return attached;
//### need to handle ACT_CASCADE
int action = processArgument(toAttach);
if ((action & OpCallbacks.ACT_RUN) == 0 &&
(action & OpCallbacks.ACT_CASCADE) != 0) {
if(_visitedNodes.contains(_broker.getStateManager(toAttach)))
return toAttach;
return handleCascade(toAttach,owner);
}
if ((action & OpCallbacks.ACT_RUN) == 0)
return toAttach;
//### need to handle ACT_RUN without also ACT_CASCADE
ClassMetaData meta = _broker.getConfiguration().
getMetaDataRepositoryInstance().getMetaData(
ImplHelper.getManagedInstance(toAttach).getClass(),
_broker.getClassLoader(), true);
return getStrategy(toAttach).attach(this, toAttach, meta, into,
owner, ownerMeta, explicit);
}
private Object handleCascade(Object toAttach, OpenJPAStateManager owner) {
StateManagerImpl sm = _broker.getStateManagerImpl(toAttach, true);
BitSet loaded = sm.getLoaded();
FieldMetaData[] fmds = sm.getMetaData().getDefinedFields();
for (FieldMetaData fmd : fmds) {
if (fmd.getElement().getCascadeAttach() == ValueMetaData.CASCADE_IMMEDIATE) {
FieldMetaData[] inverseFieldMappings = fmd.getInverseMetaDatas();
if (inverseFieldMappings.length != 0) {
_visitedNodes.add(sm);
// Only try to attach this field is it is loaded
if (loaded.get(fmd.getIndex())) {
getStrategy(toAttach).attachField(this, toAttach, sm, fmd, true);
}
}
}
}
return toAttach;
}
/**
* Determine the action to take on the given argument.
*/
private int processArgument(Object obj) {
if (_call == null)
return OpCallbacks.ACT_RUN;
return _call.processArgument(OpCallbacks.OP_ATTACH, obj,
_broker.getStateManager(obj));
}
/**
* Calculate proper attach strategy for instance.
*/
private AttachStrategy getStrategy(Object toAttach) {
PersistenceCapable pc = ImplHelper.toPersistenceCapable(toAttach,
getBroker().getConfiguration());
if (pc.pcGetStateManager() instanceof AttachStrategy)
return (AttachStrategy) pc.pcGetStateManager();
Object obj = pc.pcGetDetachedState();
if (obj instanceof AttachStrategy)
return (AttachStrategy) obj;
if (obj == null || obj == PersistenceCapable.DESERIALIZED) {
// new or detached without state
if (_version == null)
_version = new VersionAttachStrategy();
return _version;
}
// detached state
if (_detach == null)
_detach = new DetachedStateAttachStrategy();
return _detach;
}
/**
* Owning broker.
*/
BrokerImpl getBroker() {
return _broker;
}
/**
* System proxy manager.
*/
ProxyManager getProxyManager() {
return _proxy;
}
/**
* If the passed in argument has already been attached, return
* the (cached) attached copy.
*/
PersistenceCapable getAttachedCopy(Object pc) {
return ImplHelper.toPersistenceCapable(_attached.get(pc),
getBroker().getConfiguration());
}
/**
* Record the attached copy in the cache.
*/
void setAttachedCopy(Object from, PersistenceCapable into) {
_attached.put(from, into);
}
/**
* Fire before-attach event.
*/
void fireBeforeAttach(Object pc, ClassMetaData meta) {
_broker.fireLifecycleEvent(pc, null, meta,
LifecycleEvent.BEFORE_ATTACH);
}
/**
* Return the detached oid of the given instance.
*/
Object getDetachedObjectId(Object pc) {
if (pc == null)
return null;
return getStrategy(pc).getDetachedObjectId(this, pc);
}
/**
* Throw an exception if the given object is not managed; otherwise
* return its state manager.
*/
StateManagerImpl assertManaged(Object obj) {
StateManagerImpl sm = _broker.getStateManagerImpl(obj, true);
if (sm == null)
throw new UserException(_loc.get("not-managed",
Exceptions.toString(obj))).setFailedObject (obj);
return sm;
}
}