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.Iterator;
23 import java.util.Map;
24
25 import org.apache.openjpa.enhance.PersistenceCapable;
26 import org.apache.openjpa.enhance.StateManager;
27 import org.apache.openjpa.lib.util.Localizer;
28 import org.apache.openjpa.meta.ClassMetaData;
29 import org.apache.openjpa.meta.FieldMetaData;
30 import org.apache.openjpa.meta.JavaTypes;
31 import org.apache.openjpa.meta.ValueMetaData;
32 import org.apache.openjpa.meta.ValueStrategies;
33 import org.apache.openjpa.util.ApplicationIds;
34 import org.apache.openjpa.util.ObjectNotFoundException;
35 import org.apache.openjpa.util.OptimisticException;
36 import org.apache.openjpa.util.ImplHelper;
37 import org.apache.openjpa.event.LifecycleEvent;
38
39 /**
40 * Handles attaching instances using version and primary key fields.
41 *
42 * @nojavadoc
43 * @author Steve Kim
44 */
45 class VersionAttachStrategy
46 extends AttachStrategy
47 implements DetachState {
48
49 private static final Localizer _loc = Localizer.forPackage
50 (VersionAttachStrategy.class);
51
52 protected Object getDetachedObjectId(AttachManager manager,
53 Object toAttach) {
54 Broker broker = manager.getBroker();
55 ClassMetaData meta = broker.getConfiguration().
56 getMetaDataRepositoryInstance().getMetaData(
57 ImplHelper.getManagedInstance(toAttach).getClass(),
58 broker.getClassLoader(), true);
59 return ApplicationIds.create(ImplHelper.toPersistenceCapable(toAttach,
60 broker.getConfiguration()),
61 meta);
62 }
63
64 protected void provideField(Object toAttach, StateManagerImpl sm,
65 int field) {
66 sm.provideField(ImplHelper.toPersistenceCapable(toAttach,
67 sm.getContext().getConfiguration()), this, field);
68 }
69
70 public Object attach(AttachManager manager, Object toAttach,
71 ClassMetaData meta, PersistenceCapable into, OpenJPAStateManager owner,
72 ValueMetaData ownerMeta, boolean explicit) {
73 BrokerImpl broker = manager.getBroker();
74 PersistenceCapable pc = ImplHelper.toPersistenceCapable(toAttach,
75 meta.getRepository().getConfiguration());
76
77 boolean embedded = ownerMeta != null && ownerMeta.isEmbeddedPC();
78 boolean isNew = !broker.isDetached(pc);
79 Object version = null;
80 StateManagerImpl sm;
81
82 // if the state manager for the embedded instance is null, then
83 // it should be treated as a new instance (since the
84 // newly persisted owner may create a new embedded instance
85 // in the constructor); fixed bug #1075.
86 // also, if the user has attached a detached obj from somewhere
87 // else in the graph to an embedded field that was previously null,
88 // copy into a new embedded instance
89 if (embedded && (isNew || into == null
90 || broker.getStateManager(into) == null)) {
91 if (into == null)
92 into = pc.pcNewInstance(null, false);
93 sm = (StateManagerImpl) broker.embed(into, null, owner, ownerMeta);
94 into = sm.getPersistenceCapable();
95 } else if (isNew) {
96 Object oid = null;
97 if (!isPrimaryKeysGenerated(meta))
98 oid = ApplicationIds.create(pc, meta);
99
100 sm = persist(manager, pc, meta, oid, explicit);
101 into = sm.getPersistenceCapable();
102 } else if (!embedded && into == null) {
103 Object id = getDetachedObjectId(manager, toAttach);
104 if (id != null)
105 into =
106 ImplHelper.toPersistenceCapable(broker.find(id, true, null),
107 broker.getConfiguration());
108 if (into == null)
109 throw new OptimisticException(_loc.get("attach-version-del",
110 ImplHelper.getManagedInstance(pc).getClass(), id, version))
111 .setFailedObject(toAttach);
112
113 sm = manager.assertManaged(into);
114 if (meta.getDescribedType()
115 != sm.getMetaData().getDescribedType()) {
116 throw new ObjectNotFoundException(_loc.get
117 ("attach-wrongclass", id, toAttach.getClass(),
118 sm.getMetaData().getDescribedType())).
119 setFailedObject(toAttach);
120 }
121 } else
122 sm = manager.assertManaged(into);
123
124 // mark that we attached the instance *before* we
125 // fill in values to avoid endless recursion
126 manager.setAttachedCopy(toAttach, into);
127
128 // if persisting in place, just attach field values
129 if (pc == into) {
130 attachFieldsInPlace(manager, sm);
131 return into;
132 }
133
134 if (isNew) {
135 broker.fireLifecycleEvent(toAttach, null, meta,
136 LifecycleEvent.BEFORE_PERSIST);
137 } else {
138 // invoke any preAttach on the detached instance
139 manager.fireBeforeAttach(toAttach, meta);
140 }
141
142 // assign the detached pc the same state manager as the object we're
143 // copying into during the attach process
144 StateManager smBefore = pc.pcGetStateManager();
145 pc.pcReplaceStateManager(sm);
146 int detach = (isNew) ? DETACH_ALL : broker.getDetachState();
147 FetchConfiguration fetch = broker.getFetchConfiguration();
148 try {
149 FieldMetaData[] fmds = meta.getFields();
150 for (int i = 0; i < fmds.length; i++) {
151 switch (detach) {
152 case DETACH_ALL:
153 attachField(manager, toAttach, sm, fmds[i], true);
154 break;
155 case DETACH_FETCH_GROUPS:
156 if (fetch.requiresFetch(fmds[i])
157 != FetchConfiguration.FETCH_NONE)
158 attachField(manager, toAttach, sm, fmds[i], true);
159 break;
160 case DETACH_LOADED:
161 attachField(manager, toAttach, sm, fmds[i], false);
162 break;
163 }
164 }
165 } finally {
166 pc.pcReplaceStateManager(smBefore);
167 }
168 if (!embedded && !isNew)
169 compareVersion(sm, pc);
170 return ImplHelper.getManagedInstance(into);
171 }
172
173 /**
174 * Make sure the version information is correct in the detached object.
175 */
176 private void compareVersion(StateManagerImpl sm, PersistenceCapable pc) {
177 Object version = pc.pcGetVersion();
178 if (version == null)
179 return;
180
181 // don't need to load unloaded fields since its implicitly
182 // a single field value
183 StoreManager store = sm.getBroker().getStoreManager();
184 switch (store.compareVersion(sm, version, sm.getVersion())) {
185 case StoreManager.VERSION_LATER:
186 // we have a later version: set it into the object.
187 // lock validation will occur at commit time
188 sm.setVersion(version);
189 break;
190 case StoreManager.VERSION_EARLIER:
191 case StoreManager.VERSION_DIFFERENT:
192 sm.setVersion(version);
193 throw new OptimisticException(sm.getManagedInstance());
194 case StoreManager.VERSION_SAME:
195 // no action required
196 break;
197 }
198 }
199
200 /**
201 * Attach the fields of an in-place persisted instance.
202 */
203 private void attachFieldsInPlace(AttachManager manager,
204 StateManagerImpl sm) {
205 FieldMetaData[] fmds = sm.getMetaData().getFields();
206 for (int i = 0; i < fmds.length; i++) {
207 if (fmds[i].getManagement() != FieldMetaData.MANAGE_PERSISTENT)
208 continue;
209
210 Object cur, attached;
211 switch (fmds[i].getDeclaredTypeCode()) {
212 case JavaTypes.PC:
213 case JavaTypes.PC_UNTYPED:
214 cur = sm.fetchObjectField(i);
215 attached = attachInPlace(manager, sm, fmds[i], cur);
216 break;
217 case JavaTypes.ARRAY:
218 if (!fmds[i].getElement().isDeclaredTypePC())
219 continue;
220 cur = sm.fetchObjectField(i);
221 attached =
222 attachInPlace(manager, sm, fmds[i], (Object[]) cur);
223 break;
224 case JavaTypes.COLLECTION:
225 if (!fmds[i].getElement().isDeclaredTypePC())
226 continue;
227 cur = sm.fetchObjectField(i);
228 attached = attachInPlace(manager, sm, fmds[i],
229 (Collection) cur);
230 break;
231 case JavaTypes.MAP:
232 if (!fmds[i].getElement().isDeclaredTypePC()
233 && !fmds[i].getKey().isDeclaredTypePC())
234 continue;
235 cur = sm.fetchObjectField(i);
236 attached = attachInPlace(manager, sm, fmds[i], (Map) cur);
237 break;
238 default:
239 continue;
240 }
241
242 if (cur != attached)
243 sm.settingObjectField(sm.getPersistenceCapable(), i,
244 cur, attached, StateManager.SET_REMOTE);
245 }
246 }
247
248 /**
249 * Attach the given pc.
250 */
251 private Object attachInPlace(AttachManager manager, StateManagerImpl sm,
252 ValueMetaData vmd, Object pc) {
253 if (pc == null)
254 return null;
255 Object attached = manager.getAttachedCopy(pc);
256 if (attached != null)
257 return attached;
258
259 OpenJPAStateManager into = manager.getBroker().getStateManager(pc);
260 PersistenceCapable intoPC = (into == null) ? null
261 : into.getPersistenceCapable();
262 if (vmd.isEmbedded())
263 return manager.attach(pc, intoPC, sm, vmd, false);
264 return manager.attach(pc, intoPC, null, null, false);
265 }
266
267 /**
268 * Attach the given array.
269 */
270 private Object[] attachInPlace(AttachManager manager, StateManagerImpl sm,
271 FieldMetaData fmd, Object[] arr) {
272 if (arr == null)
273 return null;
274
275 for (int i = 0; i < arr.length; i++)
276 arr[i] = attachInPlace(manager, sm, fmd.getElement(), arr[i]);
277 return arr;
278 }
279
280 /**
281 * Attach the given collection.
282 */
283 private Collection attachInPlace(AttachManager manager,
284 StateManagerImpl sm, FieldMetaData fmd, Collection coll) {
285 if (coll == null || coll.isEmpty())
286 return coll;
287
288 // copy if elements embedded or contains detached, which will mean
289 // we'll have to copy the existing elements
290 Collection copy = null;
291 if (fmd.getElement().isEmbedded())
292 copy = (Collection) sm.newFieldProxy(fmd.getIndex());
293 else {
294 for (Iterator itr = coll.iterator(); itr.hasNext();) {
295 if (manager.getBroker().isDetached(itr.next())) {
296 copy = (Collection) sm.newFieldProxy(fmd.getIndex());
297 break;
298 }
299 }
300 }
301
302 Object attached;
303 for (Iterator itr = coll.iterator(); itr.hasNext();) {
304 attached = attachInPlace(manager, sm, fmd.getElement(),
305 itr.next());
306 if (copy != null)
307 copy.add(attached);
308 }
309 return (copy == null) ? coll : copy;
310 }
311
312 /**
313 * Attach the given map.
314 */
315 private Map attachInPlace(AttachManager manager, StateManagerImpl sm,
316 FieldMetaData fmd, Map map) {
317 if (map == null || map.isEmpty())
318 return map;
319
320 Map copy = null;
321 Map.Entry entry;
322 boolean keyPC = fmd.getKey().isDeclaredTypePC();
323 boolean valPC = fmd.getElement().isDeclaredTypePC();
324
325 // copy if embedded pcs or detached pcs, which will require us to
326 // copy elements
327 if (fmd.getKey().isEmbeddedPC() || fmd.getElement().isEmbeddedPC())
328 copy = (Map) sm.newFieldProxy(fmd.getIndex());
329 else {
330 for (Iterator itr = map.entrySet().iterator(); itr.hasNext();) {
331 entry = (Map.Entry) itr.next();
332 if ((keyPC && manager.getBroker().isDetached(entry.getKey()))
333 || (valPC && manager.getBroker().isDetached
334 (entry.getValue()))) {
335 copy = (Map) sm.newFieldProxy(fmd.getIndex());
336 break;
337 }
338 }
339 }
340
341 Object key, val;
342 for (Iterator itr = map.entrySet().iterator(); itr.hasNext();) {
343 entry = (Map.Entry) itr.next();
344 key = entry.getKey();
345 if (keyPC)
346 key = attachInPlace(manager, sm, fmd.getKey(), key);
347 val = entry.getValue();
348 if (valPC)
349 val = attachInPlace(manager, sm, fmd.getElement(), val);
350 if (copy != null)
351 copy.put(key, val);
352 }
353 return (copy == null) ? map : copy;
354 }
355
356 /**
357 * Find a PersistenceCapable instance of an Object if it exists in the
358 * database. If the object is null or can't be found in the database.
359 *
360 * @param pc An object which will be attached into the current context. The
361 * object may or may not correspond to a row in the database.
362 *
363 * @return If the object is null or can't be found in the database this
364 * method returns null. Otherwise a PersistenceCapable representation of the
365 * object is returned.
366 */
367 protected PersistenceCapable findFromDatabase(AttachManager manager,
368 Object pc) {
369 Object oid = manager.getBroker().newObjectId(pc.getClass(),
370 manager.getDetachedObjectId(pc));
371
372 if (oid != null) {
373 return ImplHelper.toPersistenceCapable(
374 manager.getBroker().find(oid, true, null),
375 manager.getBroker().getConfiguration());
376 } else {
377 return null;
378 }
379 }
380
381 private boolean isPrimaryKeysGenerated(ClassMetaData meta) {
382 FieldMetaData[] pks = meta.getPrimaryKeyFields();
383 for (int i = 0; i < pks.length; i++) {
384 if (pks[i].getValueStrategy() != ValueStrategies.NONE)
385 return true;
386 }
387 return false;
388 }
389 }