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.lang.reflect.Array;
22 import java.util.Collection;
23 import java.util.Iterator;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.apache.commons.lang.ObjectUtils;
28 import org.apache.openjpa.enhance.PersistenceCapable;
29 import org.apache.openjpa.enhance.StateManager;
30 import org.apache.openjpa.lib.util.Localizer;
31 import org.apache.openjpa.meta.ClassMetaData;
32 import org.apache.openjpa.meta.FieldMetaData;
33 import org.apache.openjpa.meta.JavaTypes;
34 import org.apache.openjpa.meta.ValueMetaData;
35 import org.apache.openjpa.util.Exceptions;
36 import org.apache.openjpa.util.InternalException;
37 import org.apache.openjpa.util.UserException;
38
39 /**
40 * Strategy for attaching objects.
41 *
42 * @author Marc Prud'hommeaux
43 * @author Steve Kim
44 * @nojavadoc
45 */
46 abstract class AttachStrategy
47 extends TransferFieldManager {
48
49 private static final Localizer _loc = Localizer.forPackage
50 (AttachStrategy.class);
51
52 /**
53 * Attach.
54 *
55 * @param manager manager holding cache of attached instances
56 * @param toAttach detached instance
57 * @param meta metadata for the instance being attached
58 * @param into instance we're attaching into
59 * @param owner state manager for <code>into</code>
60 * @param ownerMeta field we traversed to find <code>toAttach</code>
61 * @param explicit whether to make new instances explicitly persistent
62 */
63 public abstract Object attach(AttachManager manager,
64 Object toAttach, ClassMetaData meta, PersistenceCapable into,
65 OpenJPAStateManager owner, ValueMetaData ownerMeta, boolean explicit);
66
67 /**
68 * Return the identity of the given detached instance.
69 */
70 protected abstract Object getDetachedObjectId(AttachManager manager,
71 Object toAttach);
72
73 /**
74 * Provide the given field into this field manager.
75 */
76 protected abstract void provideField(Object toAttach, StateManagerImpl sm,
77 int field);
78
79 /**
80 * Return a PNew/PNewProvisional managed object for the given detached
81 * instance.
82 */
83 protected StateManagerImpl persist(AttachManager manager,
84 PersistenceCapable pc, ClassMetaData meta, Object appId,
85 boolean explicit) {
86 PersistenceCapable newInstance;
87 if (!manager.getCopyNew())
88 newInstance = pc;
89 else if (appId == null)
90 // datastore identity or application identity with generated keys
91 newInstance = pc.pcNewInstance(null, false);
92 else // application identity: use existing fields
93 newInstance = pc.pcNewInstance(null, appId, false);
94
95 return (StateManagerImpl) manager.getBroker().persist
96 (newInstance, appId, explicit, manager.getBehavior());
97 }
98
99 /**
100 * Attach the given field into the given instance.
101 *
102 * @param toAttach the detached persistent instance
103 * @param sm state manager for the managed instance we're copying
104 * into; <code>toAttach</code> also uses this state manager
105 * @param fmd metadata on the field we're copying
106 * @param nullLoaded if false, nulls will be considered unloaded and will
107 * not be attached
108 */
109 protected boolean attachField(AttachManager manager, Object toAttach,
110 StateManagerImpl sm, FieldMetaData fmd, boolean nullLoaded) {
111 if (fmd.isVersion()
112 || fmd.getManagement() != FieldMetaData.MANAGE_PERSISTENT)
113 return false;
114
115 PersistenceCapable into = sm.getPersistenceCapable();
116 int i = fmd.getIndex();
117 provideField(toAttach, sm, i);
118
119 int set = StateManager.SET_ATTACH;
120 Object val;
121 switch (fmd.getDeclaredTypeCode()) {
122 case JavaTypes.BOOLEAN:
123 sm.settingBooleanField(into, i, sm.fetchBooleanField(i),
124 fetchBooleanField(i), set);
125 break;
126 case JavaTypes.BYTE:
127 sm.settingByteField(into, i, sm.fetchByteField(i),
128 fetchByteField(i), set);
129 break;
130 case JavaTypes.CHAR:
131 sm.settingCharField(into, i, sm.fetchCharField(i),
132 fetchCharField(i), set);
133 break;
134 case JavaTypes.DOUBLE:
135 sm.settingDoubleField(into, i, sm.fetchDoubleField(i),
136 fetchDoubleField(i), set);
137 break;
138 case JavaTypes.FLOAT:
139 sm.settingFloatField(into, i, sm.fetchFloatField(i),
140 fetchFloatField(i), set);
141 break;
142 case JavaTypes.INT:
143 sm.settingIntField(into, i, sm.fetchIntField(i),
144 fetchIntField(i), set);
145 break;
146 case JavaTypes.LONG:
147 sm.settingLongField(into, i, sm.fetchLongField(i),
148 fetchLongField(i), set);
149 break;
150 case JavaTypes.SHORT:
151 sm.settingShortField(into, i, sm.fetchShortField(i),
152 fetchShortField(i), set);
153 break;
154 case JavaTypes.STRING:
155 String sval = fetchStringField(i);
156 if (sval == null && !nullLoaded)
157 return false;
158 sm.settingStringField(into, i, sm.fetchStringField(i), sval,
159 set);
160 break;
161 case JavaTypes.DATE:
162 case JavaTypes.CALENDAR:
163 case JavaTypes.NUMBER:
164 case JavaTypes.BOOLEAN_OBJ:
165 case JavaTypes.BYTE_OBJ:
166 case JavaTypes.CHAR_OBJ:
167 case JavaTypes.DOUBLE_OBJ:
168 case JavaTypes.FLOAT_OBJ:
169 case JavaTypes.INT_OBJ:
170 case JavaTypes.LONG_OBJ:
171 case JavaTypes.SHORT_OBJ:
172 case JavaTypes.BIGDECIMAL:
173 case JavaTypes.BIGINTEGER:
174 case JavaTypes.LOCALE:
175 case JavaTypes.OBJECT:
176 case JavaTypes.OID:
177 val = fetchObjectField(i);
178 if (val == null && !nullLoaded)
179 return false;
180 sm.settingObjectField(into, i, sm.fetchObjectField(i), val,
181 set);
182 break;
183 case JavaTypes.PC:
184 case JavaTypes.PC_UNTYPED:
185 Object frmpc = fetchObjectField(i);
186 if (frmpc == null && !nullLoaded)
187 return false;
188 OpenJPAStateManager tosm = manager.getBroker().getStateManager
189 (sm.fetchObjectField(i));
190 PersistenceCapable topc = (tosm == null) ? null
191 : tosm.getPersistenceCapable();
192 if (frmpc != null || topc != null) {
193 if (fmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE)
194 frmpc = getReference(manager, frmpc, sm, fmd);
195 else {
196 PersistenceCapable intopc = topc;
197 if (!fmd.isEmbeddedPC() && frmpc != null && topc != null
198 && !ObjectUtils.equals(topc.pcFetchObjectId(),
199 manager.getDetachedObjectId(frmpc))) {
200 intopc = null;
201 }
202 frmpc = manager.attach(frmpc, intopc, sm, fmd, false);
203 }
204 if (frmpc != topc)
205 sm.settingObjectField(into, i, topc, frmpc, set);
206 }
207 break;
208 case JavaTypes.COLLECTION:
209 Collection frmc = (Collection) fetchObjectField(i);
210 if (frmc == null && !nullLoaded)
211 return false;
212 Collection toc = (Collection) sm.fetchObjectField(i);
213 if ((toc != null && !toc.isEmpty())
214 || frmc != null && !frmc.isEmpty()) {
215 if (frmc == null)
216 sm.settingObjectField(into, i, toc, null, set);
217 else if (toc == null) {
218 sm.settingObjectField(into, i, null,
219 attachCollection(manager, frmc, sm, fmd), set);
220 } else if (toc instanceof Set && frmc instanceof Set)
221 replaceCollection(manager, frmc, toc, sm, fmd);
222 else {
223 sm.settingObjectField(into, i, toc,
224 replaceList(manager, frmc, toc, sm, fmd), set);
225 }
226 }
227 break;
228 case JavaTypes.MAP:
229 Map frmm = (Map) fetchObjectField(i);
230 if (frmm == null && !nullLoaded)
231 return false;
232 Map tom = (Map) sm.fetchObjectField(i);
233 if ((tom != null && !tom.isEmpty())
234 || (frmm != null && !frmm.isEmpty())) {
235 if (frmm == null)
236 sm.settingObjectField(into, i, tom, null, set);
237 else if (tom == null)
238 sm.settingObjectField(into, i, null,
239 attachMap(manager, frmm, sm, fmd), set);
240 else
241 replaceMap(manager, frmm, tom, sm, fmd);
242 }
243 break;
244 case JavaTypes.ARRAY:
245 Object frma = fetchObjectField(i);
246 if (frma == null && !nullLoaded)
247 return false;
248 Object toa = sm.fetchObjectField(i);
249 if ((toa != null && Array.getLength(toa) > 0)
250 || (frma != null && Array.getLength(frma) > 0)) {
251 if (frma == null)
252 sm.settingObjectField(into, i, toa, null, set);
253 else
254 sm.settingObjectField(into, i, toa,
255 replaceArray(manager, frma, toa, sm, fmd), set);
256 }
257 break;
258 default:
259 throw new InternalException(fmd.toString());
260 }
261 return true;
262 }
263
264 /**
265 * Return a managed, possibly hollow reference for the given detached
266 * object.
267 */
268 protected Object getReference(AttachManager manager, Object toAttach,
269 OpenJPAStateManager sm, ValueMetaData vmd) {
270 if (toAttach == null)
271 return null;
272
273 if (manager.getBroker().isNew(toAttach)
274 || manager.getBroker().isPersistent(toAttach)) {
275 return toAttach;
276 } else if (manager.getBroker().isDetached(toAttach)) {
277 Object oid = manager.getDetachedObjectId(toAttach);
278 if (oid != null)
279 return manager.getBroker().find(oid, false, null);
280 }
281 throw new UserException(_loc.get("cant-cascade-attach", vmd))
282 .setFailedObject(toAttach);
283 }
284
285 /**
286 * Replace the contents of <code>toc</code> with the contents of
287 * <code>frmc</code>. Neither collection is null.
288 */
289 private void replaceCollection(AttachManager manager, Collection frmc,
290 Collection toc, OpenJPAStateManager sm, FieldMetaData fmd) {
291 // if frmc collection is empty, just clear toc
292 if (frmc.isEmpty()) {
293 if (!toc.isEmpty())
294 toc.clear();
295 return;
296 }
297
298 // if this is a pc collection, attach all instances
299 boolean pc = fmd.getElement().isDeclaredTypePC();
300 if (pc)
301 frmc = attachCollection(manager, frmc, sm, fmd);
302
303 // remove all elements from the toc collection that aren't in frmc
304 toc.retainAll(frmc);
305
306 // now add all elements that are in frmc but not toc
307 if (frmc.size() != toc.size()) {
308 for (Iterator i = frmc.iterator(); i.hasNext();) {
309 Object ob = i.next();
310 if (!toc.contains(ob))
311 toc.add(ob);
312 }
313 }
314 }
315
316 /**
317 * Return a new collection with the attached contents of the given one.
318 */
319 protected Collection attachCollection(AttachManager manager,
320 Collection orig, OpenJPAStateManager sm, FieldMetaData fmd) {
321 Collection coll = copyCollection(manager, orig, fmd);
322 ValueMetaData vmd = fmd.getElement();
323 if (!vmd.isDeclaredTypePC())
324 return coll;
325
326 // unfortunately we have to clear the original and re-add
327 coll.clear();
328 Object elem;
329 for (Iterator itr = orig.iterator(); itr.hasNext();) {
330 if (vmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE)
331 elem = getReference(manager, itr.next(), sm, vmd);
332 else
333 elem = manager.attach(itr.next(), null, sm, vmd, false);
334 coll.add(elem);
335 }
336 return coll;
337 }
338
339 /**
340 * Copies the given collection.
341 */
342 private Collection copyCollection(AttachManager manager, Collection orig,
343 FieldMetaData fmd) {
344 Collection coll = manager.getProxyManager().copyCollection(orig);
345 if (coll == null)
346 throw new UserException(_loc.get("not-copyable", fmd));
347 return coll;
348 }
349
350 /**
351 * Returns an attached version of the <code>frml</code>
352 * list if it is different than <code>tol</code>. If the lists
353 * will be identical, returns <code>tol</code>. Neither list is null.
354 */
355 private Collection replaceList(AttachManager manager, Collection frml,
356 Collection tol, OpenJPAStateManager sm, FieldMetaData fmd) {
357 boolean pc = fmd.getElement().isDeclaredTypePC();
358 if (pc)
359 frml = attachCollection(manager, frml, sm, fmd);
360
361 // if the only diff between frml and tol is some added elements at
362 // the end, make the changes directly in tol
363 if (frml.size() >= tol.size()) {
364 Iterator frmi = frml.iterator();
365 for (Iterator toi = tol.iterator(); toi.hasNext();) {
366 // if there's an incompatibility, just return a copy of frml
367 // (it's already copied if we attached it)
368 if (!equals(frmi.next(), toi.next(), pc))
369 return (pc) ? frml : copyCollection(manager, frml, fmd);
370 }
371
372 // just add the extra elements in frml to tol and return tol
373 while (frmi.hasNext())
374 tol.add(frmi.next());
375 return tol;
376 }
377
378 // the lists are different; just make sure frml is copied and return it
379 return (pc) ? frml : copyCollection(manager, frml, fmd);
380 }
381
382 /**
383 * Replace the contents of <code>tom</code> with the contents of
384 * <code>frmm</code>. Neither map is null.
385 */
386 private void replaceMap(AttachManager manager, Map frmm, Map tom,
387 OpenJPAStateManager sm, FieldMetaData fmd) {
388 if (frmm.isEmpty()) {
389 if (!tom.isEmpty())
390 tom.clear();
391 return;
392 }
393
394 // if this is a pc map, attach all instances
395 boolean keyPC = fmd.getKey().isDeclaredTypePC();
396 boolean valPC = fmd.getElement().isDeclaredTypePC();
397 if (keyPC || valPC)
398 frmm = attachMap(manager, frmm, sm, fmd);
399
400 // make sure all the keys in the from map are in the two map, and
401 // that they have the same values
402 for (Iterator i = frmm.entrySet().iterator(); i.hasNext();) {
403 Map.Entry entry = (Map.Entry) i.next();
404 if (!tom.containsKey(entry.getKey())
405 || !equals(tom.get(entry.getKey()), entry.getValue(), valPC)) {
406 tom.put(entry.getKey(), entry.getValue());
407 }
408 }
409
410 // remove any keys in the to map that aren't in the from map
411 if (tom.size() != frmm.size()) {
412 for (Iterator i = tom.keySet().iterator(); i.hasNext();) {
413 if (!(frmm.containsKey(i.next())))
414 i.remove();
415 }
416 }
417 }
418
419 /**
420 * Make sure all the values in the given map are attached.
421 */
422 protected Map attachMap(AttachManager manager, Map orig,
423 OpenJPAStateManager sm, FieldMetaData fmd) {
424 Map map = manager.getProxyManager().copyMap(orig);
425 if (map == null)
426 throw new UserException(_loc.get("not-copyable", fmd));
427
428 ValueMetaData keymd = fmd.getKey();
429 ValueMetaData valmd = fmd.getElement();
430 if (!keymd.isDeclaredTypePC() && !valmd.isDeclaredTypePC())
431 return map;
432
433 // if we have to replace keys, just clear and re-add; otherwise
434 // we can use the entry set to reset the values only
435 Map.Entry entry;
436 if (keymd.isDeclaredTypePC()) {
437 map.clear();
438 Object key, val;
439 for (Iterator itr = orig.entrySet().iterator(); itr.hasNext();) {
440 entry = (Map.Entry) itr.next();
441 key = entry.getKey();
442 if (keymd.getCascadeAttach() == ValueMetaData.CASCADE_NONE)
443 key = getReference(manager, key, sm, keymd);
444 else
445 key = manager.attach(key, null, sm, keymd, false);
446 val = entry.getValue();
447 if (valmd.isDeclaredTypePC()) {
448 if (valmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE)
449 val = getReference(manager, val, sm, valmd);
450 else
451 val = manager.attach(val, null, sm, valmd, false);
452 }
453 map.put(key, val);
454 }
455 } else {
456 Object val;
457 for (Iterator itr = map.entrySet().iterator(); itr.hasNext();) {
458 entry = (Map.Entry) itr.next();
459 if (valmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE)
460 val = getReference(manager, entry.getValue(), sm, valmd);
461 else
462 val = manager.attach(entry.getValue(), null, sm, valmd,
463 false);
464 entry.setValue(val);
465 }
466 }
467 return map;
468 }
469
470 /**
471 * Returns an attached version of the <code>frma</code>
472 * array if it is different than <code>toa</code>. If the arrays
473 * will be identical, returns <code>toa</code>.
474 */
475 private Object replaceArray(AttachManager manager, Object frma,
476 Object toa, OpenJPAStateManager sm, FieldMetaData fmd) {
477 int len = Array.getLength(frma);
478 boolean diff = toa == null || len != Array.getLength(toa);
479
480 // populate an array copy on the initial assumption that the array
481 // is dirty
482 Object newa = Array.newInstance(fmd.getElement().getDeclaredType(),
483 len);
484 ValueMetaData vmd = fmd.getElement();
485 boolean pc = vmd.isDeclaredTypePC();
486 Object elem;
487 for (int i = 0; i < len; i++) {
488 elem = Array.get(frma, i);
489 if (pc) {
490 if (vmd.getCascadeAttach() == ValueMetaData.CASCADE_NONE)
491 elem = getReference(manager, elem, sm, vmd);
492 else
493 elem = manager.attach(elem, null, sm, vmd, false);
494 }
495 diff = diff || !equals(elem, Array.get(toa, i), pc);
496 Array.set(newa, i, elem);
497 }
498 return (diff) ? newa : toa;
499 }
500
501 /**
502 * Return true if the given objects are equal. PCs are compared for
503 * on JVM identity.
504 */
505 private static boolean equals(Object a, Object b, boolean pc) {
506 if (a == b)
507 return true;
508 if (pc || a == null || b == null)
509 return false;
510 return a.equals (b);
511 }
512 }