| /* |
| * 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; |
| } |
| } |