1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.kernel;
20
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.Iterator;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28
29 import org.apache.commons.collections.map.IdentityMap;
30 import org.apache.openjpa.enhance.PersistenceCapable;
31 import org.apache.openjpa.event.CallbackModes;
32 import org.apache.openjpa.event.LifecycleEvent;
33 import org.apache.openjpa.lib.util.Localizer;
34 import org.apache.openjpa.meta.ClassMetaData;
35 import org.apache.openjpa.meta.ValueMetaData;
36 import org.apache.openjpa.util.CallbackException;
37 import org.apache.openjpa.util.Exceptions;
38 import org.apache.openjpa.util.OpenJPAException;
39 import org.apache.openjpa.util.OptimisticException;
40 import org.apache.openjpa.util.ProxyManager;
41 import org.apache.openjpa.util.UserException;
42 import org.apache.openjpa.util.ImplHelper;
43
44 /**
45 * Handles attaching instances.
46 *
47 * @author Marc Prud'hommeaux
48 */
49 class AttachManager {
50
51 private static final Localizer _loc = Localizer.forPackage
52 (AttachManager.class);
53
54 private final BrokerImpl _broker;
55 private final ProxyManager _proxy;
56 private final OpCallbacks _call;
57 private final boolean _copyNew;
58 private final boolean _failFast;
59 private final IdentityMap _attached = new IdentityMap();
60
61 // reusable strategies
62 private AttachStrategy _version = null;
63 private AttachStrategy _detach = null;
64
65 /**
66 * Constructor. Supply broker attaching to.
67 */
68 public AttachManager(BrokerImpl broker, boolean copyNew, OpCallbacks call) {
69 _broker = broker;
70 _proxy = broker.getConfiguration().getProxyManagerInstance();
71 _call = call;
72 _copyNew = copyNew;
73 _failFast = (broker.getConfiguration().getMetaDataRepositoryInstance().
74 getMetaDataFactory().getDefaults().getCallbackMode()
75 & CallbackModes.CALLBACK_FAIL_FAST) != 0;
76 }
77
78 /**
79 * Return the behavior supplied on construction.
80 */
81 public OpCallbacks getBehavior() {
82 return _call;
83 }
84
85 /**
86 * Return whether to copy new instances being persisted.
87 */
88 public boolean getCopyNew() {
89 return _copyNew;
90 }
91
92 /**
93 * Return an attached version of the given instance.
94 */
95 public Object attach(Object pc) {
96 if (pc == null)
97 return null;
98
99 CallbackException excep = null;
100 try {
101 return attach(pc, null, null, null, true);
102 } catch (CallbackException ce) {
103 excep = ce;
104 return null; // won't be reached as the exceps will be rethrown
105 } finally {
106 List exceps = null;
107 if (excep == null || !_failFast)
108 exceps = invokeAfterAttach(null);
109 else
110 exceps = Collections.singletonList(excep);
111 _attached.clear();
112 throwExceptions(exceps, null, false);
113 }
114 }
115
116 /**
117 * Return attached versions of the given instances.
118 */
119 public Object[] attachAll(Collection instances) {
120 Object[] attached = new Object[instances.size()];
121 List exceps = null;
122 List failed = null;
123 boolean opt = true;
124 boolean failFast = false;
125 try {
126 int i = 0;
127 for (Iterator itr = instances.iterator(); itr.hasNext(); i++) {
128 try {
129 attached[i] = attach(itr.next(), null, null, null, true);
130 } catch (OpenJPAException ke) {
131 // track exceptions and optimistic failed objects
132 if (opt && !(ke instanceof OptimisticException))
133 opt = false;
134 if (opt && ke.getFailedObject() != null)
135 failed = add(failed, ke.getFailedObject());
136 exceps = add(exceps, ke);
137
138 if (ke instanceof CallbackException && _failFast) {
139 failFast = true;
140 break;
141 }
142 }
143 catch (RuntimeException re) {
144 exceps = add(exceps, re);
145 }
146 }
147 } finally {
148 // invoke post callbacks unless all failed
149 if (!failFast && (exceps == null
150 || exceps.size() < instances.size()))
151 exceps = invokeAfterAttach(exceps);
152 _attached.clear();
153 }
154 throwExceptions(exceps, failed, opt);
155 return attached;
156 }
157
158 /**
159 * Invoke postAttach() on any attached instances that implement
160 * PostAttachCallback. This will be done after the entire graph has
161 * been attached.
162 */
163 private List invokeAfterAttach(List exceps) {
164 Set entries = _attached.entrySet();
165 for (Iterator i = entries.iterator(); i.hasNext();) {
166 Map.Entry entry = (Map.Entry) i.next();
167 Object attached = entry.getValue();
168 StateManagerImpl sm = _broker.getStateManagerImpl(attached, true);
169 if (sm.isNew())
170 continue;
171 try {
172 _broker.fireLifecycleEvent(attached, entry.getKey(),
173 sm.getMetaData(), LifecycleEvent.AFTER_ATTACH);
174 } catch (RuntimeException re) {
175 exceps = add(exceps, re);
176 if (_failFast && re instanceof CallbackException)
177 break;
178 }
179 }
180 return exceps;
181 }
182
183 /**
184 * Add an object to the list.
185 */
186 private List add(List list, Object obj) {
187 if (list == null)
188 list = new LinkedList();
189 list.add(obj);
190 return list;
191 }
192
193 /**
194 * Throw exception for failures.
195 */
196 private void throwExceptions(List exceps, List failed, boolean opt) {
197 if (exceps == null)
198 return;
199 if (exceps.size() == 1)
200 throw (RuntimeException) exceps.get(0);
201
202 Throwable[] t = (Throwable[]) exceps.toArray
203 (new Throwable[exceps.size()]);
204 if (opt && failed != null)
205 throw new OptimisticException(failed, t);
206 if (opt)
207 throw new OptimisticException(t);
208 throw new UserException(_loc.get("nested-exceps")).
209 setNestedThrowables(t);
210 }
211
212 /**
213 * Attach.
214 *
215 * @param toAttach the detached object
216 * @param into the instance we're attaching into
217 * @param owner state manager for <code>into</code>
218 * @param ownerMeta the field we traversed to find <code>toAttach</code>
219 * @param explicit whether to make new instances explicitly persistent
220 */
221 Object attach(Object toAttach, PersistenceCapable into,
222 OpenJPAStateManager owner, ValueMetaData ownerMeta, boolean explicit) {
223 if (toAttach == null)
224 return null;
225
226 // check if already attached
227 Object attached = _attached.get(toAttach);
228 if (attached != null)
229 return attached;
230
231 //### need to handle ACT_CASCADE
232 int action = processArgument(toAttach);
233 if ((action & OpCallbacks.ACT_RUN) == 0)
234 return toAttach;
235
236 //### need to handle ACT_RUN without also ACT_CASCADE
237 ClassMetaData meta = _broker.getConfiguration().
238 getMetaDataRepositoryInstance().getMetaData(
239 ImplHelper.getManagedInstance(toAttach).getClass(),
240 _broker.getClassLoader(), true);
241 return getStrategy(toAttach).attach(this, toAttach, meta, into,
242 owner, ownerMeta, explicit);
243 }
244
245 /**
246 * Determine the action to take on the given argument.
247 */
248 private int processArgument(Object obj) {
249 if (_call == null)
250 return OpCallbacks.ACT_RUN;
251 return _call.processArgument(OpCallbacks.OP_ATTACH, obj,
252 _broker.getStateManager(obj));
253 }
254
255 /**
256 * Calculate proper attach strategy for instance.
257 */
258 private AttachStrategy getStrategy(Object toAttach) {
259 PersistenceCapable pc = ImplHelper.toPersistenceCapable(toAttach,
260 getBroker().getConfiguration());
261 if (pc.pcGetStateManager() instanceof AttachStrategy)
262 return (AttachStrategy) pc.pcGetStateManager();
263
264 Object obj = pc.pcGetDetachedState();
265 if (obj instanceof AttachStrategy)
266 return (AttachStrategy) obj;
267 if (obj == null || obj == PersistenceCapable.DESERIALIZED) {
268 // new or detached without state
269 if (_version == null)
270 _version = new VersionAttachStrategy();
271 return _version;
272 }
273
274 // detached state
275 if (_detach == null)
276 _detach = new DetachedStateAttachStrategy();
277 return _detach;
278 }
279
280 /**
281 * Owning broker.
282 */
283 BrokerImpl getBroker() {
284 return _broker;
285 }
286
287 /**
288 * System proxy manager.
289 */
290 ProxyManager getProxyManager() {
291 return _proxy;
292 }
293
294 /**
295 * If the passed in argument has already been attached, return
296 * the (cached) attached copy.
297 */
298 PersistenceCapable getAttachedCopy(Object pc) {
299 return ImplHelper.toPersistenceCapable(_attached.get(pc),
300 getBroker().getConfiguration());
301 }
302
303 /**
304 * Record the attached copy in the cache.
305 */
306 void setAttachedCopy(Object from, PersistenceCapable into) {
307 _attached.put(from, into);
308 }
309
310 /**
311 * Fire before-attach event.
312 */
313 void fireBeforeAttach(Object pc, ClassMetaData meta) {
314 _broker.fireLifecycleEvent(pc, null, meta,
315 LifecycleEvent.BEFORE_ATTACH);
316 }
317
318 /**
319 * Return the detached oid of the given instance.
320 */
321 Object getDetachedObjectId(Object pc) {
322 if (pc == null)
323 return null;
324 return getStrategy(pc).getDetachedObjectId(this, pc);
325 }
326
327 /**
328 * Throw an exception if the given object is not managed; otherwise
329 * return its state manager.
330 */
331 StateManagerImpl assertManaged(Object obj) {
332 StateManagerImpl sm = _broker.getStateManagerImpl(obj, true);
333 if (sm == null)
334 throw new UserException(_loc.get("not-managed",
335 Exceptions.toString(obj))).setFailedObject (obj);
336 return sm;
337 }
338 }