001: package org.apache.ojb.odmg;
002:
003: /* Copyright 2002-2005 The Apache Software Foundation
004: *
005: * Licensed under the Apache License, Version 2.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: import java.util.ArrayList;
019: import java.util.Collection;
020: import java.util.Collections;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Map;
025:
026: import org.apache.commons.lang.ClassUtils;
027: import org.apache.ojb.broker.Identity;
028: import org.apache.ojb.broker.IdentityFactory;
029: import org.apache.ojb.broker.OJBRuntimeException;
030: import org.apache.ojb.broker.PersistenceBrokerInternal;
031: import org.apache.ojb.broker.core.proxy.CollectionProxy;
032: import org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl;
033: import org.apache.ojb.broker.core.proxy.CollectionProxyListener;
034: import org.apache.ojb.broker.core.proxy.ProxyHelper;
035: import org.apache.ojb.broker.metadata.CollectionDescriptor;
036: import org.apache.ojb.broker.metadata.FieldType;
037: import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
038: import org.apache.ojb.broker.util.BrokerHelper;
039: import org.apache.ojb.broker.util.logging.Logger;
040: import org.apache.ojb.broker.util.logging.LoggerFactory;
041:
042: /**
043: * This class encapsulates classes used to take persistence capable object
044: * state snapshoot and to detect changed fields or references.
045: *
046: * @version $Id: Image.java,v 1.1.2.2 2005/12/21 22:29:21 tomdz Exp $
047: */
048: public abstract class Image {
049: static Logger log = LoggerFactory.getLogger(Image.class);
050: private long timestamp = System.currentTimeMillis();
051:
052: private Image() {
053: }
054:
055: boolean illegalImageComparison(Image oldImage) {
056: return timestamp < oldImage.timestamp;
057: }
058:
059: public abstract void cleanup(boolean reuse);
060:
061: public abstract boolean modified(Image other);
062:
063: abstract void referenceProcessing(Image oldImage);
064:
065: public void performReferenceDetection(Image oldImage) {
066: if (illegalImageComparison(oldImage)) {
067: throw new ImageException(
068: "The specified Image object is newer than current one, wrong Image order!");
069: }
070: referenceProcessing(oldImage);
071: }
072:
073: //===================================================================
074: // inner class
075: //===================================================================
076: public static class MultipleRef extends Image implements
077: CollectionProxyListener {
078: static final int IS_NORMAL_OBJECT = 11;
079: static final int IS_MATERIALIZED_PROXY = 13;
080: static final int IS_UNMATERIALIZED_PROXY = 17;
081:
082: private ImageListener listener;
083: private final CollectionDescriptor cod;
084: private final Object collectionOrArray;
085: private Map references;
086: private int status;
087: private boolean hasTransientIdentity;
088: private boolean isRefreshed;
089:
090: public MultipleRef(ImageListener listener,
091: CollectionDescriptor cod, Object collectionOrArray) {
092: this .listener = listener;
093: this .cod = cod;
094: this .collectionOrArray = collectionOrArray;
095: this .isRefreshed = true;
096: this .hasTransientIdentity = false;
097: this .references = Collections.EMPTY_MAP;
098: init();
099: }
100:
101: private void init() {
102: CollectionProxy colProxy = ProxyHelper
103: .getCollectionProxy(collectionOrArray);
104: if (colProxy != null) {
105: if (colProxy.isLoaded()) {
106: status = IS_MATERIALIZED_PROXY;
107: /*
108: TODO: avoid this cast
109: e.g. change CollectionProxy interface - CollectionProxy should
110: extend Collection to support Iterator
111: */
112: handleReferencedObjects(((Collection) colProxy)
113: .iterator());
114: } else {
115: status = IS_UNMATERIALIZED_PROXY;
116: if (log.isDebugEnabled())
117: log
118: .debug("Unmaterialized proxy collection, use proxy listener");
119: colProxy.addListener(this );
120: }
121: } else {
122: status = IS_NORMAL_OBJECT;
123: if (collectionOrArray != null) {
124: Iterator it = BrokerHelper
125: .getCollectionIterator(collectionOrArray);
126: handleReferencedObjects(it);
127: }
128: }
129: }
130:
131: void handleReferencedObjects(Iterator it) {
132: if (it == null)
133: return;
134: references = new HashMap();
135: if (log.isDebugEnabled())
136: log.debug("Handle collection references");
137: IdentityFactory idFac = listener.getBroker()
138: .serviceIdentity();
139: Identity oid;
140: Object obj;
141: while (it.hasNext()) {
142: obj = it.next();
143: oid = idFac.buildIdentity(obj);
144: if (!hasTransientIdentity && oid.isTransient()) {
145: hasTransientIdentity = true;
146: }
147: references.put(oid, obj);
148: }
149: }
150:
151: public void cleanup(boolean reuse) {
152: if (log.isDebugEnabled())
153: log.debug("Cleanup collection image, reuse=" + reuse);
154: if (reuse) {
155: isRefreshed = false;
156: } else {
157: if (status == IS_UNMATERIALIZED_PROXY) {
158: CollectionProxy colProxy = ProxyHelper
159: .getCollectionProxy(collectionOrArray);
160: if (colProxy != null) {
161: colProxy.removeListener(this );
162: }
163: }
164: }
165: }
166:
167: void referenceProcessing(Image oldImage) {
168: MultipleRef oldRefs = (MultipleRef) oldImage;
169: if (incommensurableProxies(oldRefs)) {
170: if (isUnmaterializedProxy())
171: handleReferencedObjects(BrokerHelper
172: .getCollectionIterator(collectionOrArray));
173: if (oldRefs.isUnmaterializedProxy())
174: oldRefs
175: .handleReferencedObjects(BrokerHelper
176: .getCollectionIterator(oldRefs.collectionOrArray));
177: }
178: if (!isRefreshed)
179: refreshIdentities();
180: if (!oldRefs.isRefreshed)
181: oldRefs.refreshIdentities();
182:
183: // find deleted reference objects
184: if (oldRefs.references.size() > 0) {
185: Iterator oldIter = oldRefs.references.entrySet()
186: .iterator();
187: while (oldIter.hasNext()) {
188: Map.Entry entry = (Map.Entry) oldIter.next();
189: Identity oldOid = (Identity) entry.getKey();
190: /*
191: search for deleted objects: if in the new image an object
192: from the old image is not contained, we found a deleted object
193: */
194: if (!isUnmaterializedProxy()
195: && !containsReference(oldOid)) {
196: listener.deletedXToN(cod, entry.getValue(),
197: oldOid);
198: }
199: }
200: }
201:
202: // find new reference objects
203: if (references.size() > 0) {
204: Iterator newIter = references.entrySet().iterator();
205: while (newIter.hasNext()) {
206: Map.Entry entry = (Map.Entry) newIter.next();
207: Identity newOid = (Identity) entry.getKey();
208: /*
209: search for added objects: if in the old image an object
210: from the new image is not contained, we found a added object
211: */
212: if (!oldRefs.containsReference(newOid)) {
213: listener.addedXToN(cod, entry.getValue(),
214: newOid);
215: }
216: }
217: }
218: }
219:
220: /**
221: * To detect deleted (added) collection objects it's necessary iterate over the old (new) image collection.
222: * If the old (new) image collection is a unmaterialized proxy we have to check if the new (old) image collection
223: * is the same proxy instance or not.
224: * E.g. if the user exchange one another the unmaterialized proxy collection objects of two main objects,
225: * then both proxy need to be materialized to assign the changed FK field values.
226: */
227: private boolean incommensurableProxies(MultipleRef oldImage) {
228: boolean result = false;
229: // deleted objects
230: if (oldImage.isUnmaterializedProxy()
231: || isUnmaterializedProxy()) {
232: result = !collectionOrArray
233: .equals(oldImage.collectionOrArray);
234: }
235: return result;
236: }
237:
238: private void refreshIdentities() {
239: // if no transient identities are used, nothing to do
240: if (hasTransientIdentity && references.size() > 0) {
241: hasTransientIdentity = false;
242: // we need independent key list from Map
243: List list = new ArrayList(references.keySet());
244: IdentityFactory idFac = listener.getBroker()
245: .serviceIdentity();
246: Identity oid, newOid;
247: Object obj;
248: for (int i = 0; i < list.size(); i++) {
249: oid = (Identity) list.get(i);
250: if (oid.isTransient()) {
251: obj = references.remove(oid);
252: newOid = idFac.buildIdentity(obj);
253: references.put(newOid, obj);
254: if (!hasTransientIdentity && oid.isTransient()) {
255: hasTransientIdentity = true;
256: }
257: }
258: }
259: isRefreshed = true;
260: }
261: }
262:
263: /**
264: * Always return 'false', because changed 1:n or m:n references do not
265: * affect the main object.
266: */
267: public boolean modified(Image other) {
268: return false;
269: }
270:
271: boolean containsReference(Identity oid) {
272: if (!isRefreshed)
273: refreshIdentities();
274: return references.containsKey(oid);
275: }
276:
277: Map getIdentityReferenceObjectMap() {
278: if (!isRefreshed)
279: refreshIdentities();
280: return references;
281: }
282:
283: boolean isMaterializedProxy() {
284: return status == IS_MATERIALIZED_PROXY;
285: }
286:
287: boolean isUnmaterializedProxy() {
288: return status == IS_UNMATERIALIZED_PROXY;
289: }
290:
291: // CollectionProxy Listener methods
292: //---------------------------------
293: public void beforeLoading(CollectionProxyDefaultImpl colProxy) {
294: //noop
295: }
296:
297: public void afterLoading(CollectionProxyDefaultImpl colProxy) {
298: if (status == IS_UNMATERIALIZED_PROXY) {
299: status = IS_MATERIALIZED_PROXY;
300: handleReferencedObjects(colProxy.iterator());
301: colProxy.removeListener(this );
302: }
303: }
304:
305: public String toString() {
306: return ClassUtils.getShortClassName(this .getClass())
307: + "[references-size="
308: + (references != null ? "" + references.size()
309: : "undefined") + "]";
310: }
311: }
312:
313: //===================================================================
314: // inner class
315: //===================================================================
316: public static class SingleRef extends Image {
317: private Object referenceObjOrProxy;
318: private Identity oid = null;
319: private final ImageListener listener;
320: private final ObjectReferenceDescriptor ord;
321:
322: public SingleRef(ImageListener listener,
323: ObjectReferenceDescriptor ord, Object reference) {
324: this .listener = listener;
325: this .ord = ord;
326: this .referenceObjOrProxy = reference;
327: }
328:
329: public void cleanup(boolean reuse) {
330: if (!reuse) {
331: referenceObjOrProxy = null;
332: }
333: }
334:
335: void referenceProcessing(Image oldImage) {
336: SingleRef oldRef = (SingleRef) oldImage;
337: boolean isSame = getReferenceObjectOrProxy() == oldRef
338: .getReferenceObjectOrProxy();
339: if (!isSame) {
340: Identity newOid = getIdentity();
341: Identity oldOid = oldRef.getIdentity();
342: if (newOid == null) {
343: if (oldOid != null) {
344: listener.deletedOneToOne(ord, oldRef
345: .getReferenceObjectOrProxy(), oldOid,
346: true);
347: }
348: } else {
349: if (oldOid == null) {
350: listener.addedOneToOne(ord,
351: getReferenceObjectOrProxy(), newOid);
352: } else {
353: if (!newOid.equals(oldOid)) {
354: listener.deletedOneToOne(ord, oldRef
355: .getReferenceObjectOrProxy(),
356: oldOid, false);
357: listener
358: .addedOneToOne(
359: ord,
360: getReferenceObjectOrProxy(),
361: newOid);
362: }
363: }
364: }
365: }
366: }
367:
368: public Object getReferenceObjectOrProxy() {
369: return referenceObjOrProxy;
370: }
371:
372: private Identity getIdentity() {
373: if (oid == null || oid.isTransient()) {
374: if (referenceObjOrProxy != null) {
375: oid = listener.getBroker().serviceIdentity()
376: .buildIdentity(referenceObjOrProxy);
377: }
378: }
379: return oid;
380: }
381:
382: /**
383: * If a 1:1 reference has changed it will
384: * affects the main object (FK needs update).
385: */
386: public boolean modified(Image toCompare) {
387: boolean modified = false;
388: if (!(this == toCompare)) {
389: if (toCompare instanceof Image.SingleRef) {
390: Image.SingleRef other = (Image.SingleRef) toCompare;
391: Identity current = getIdentity();
392: Identity otherOid = other.getIdentity();
393: modified = current != null ? !current
394: .equals(otherOid) : !(otherOid == null);
395: }
396: }
397: return modified;
398: }
399:
400: public String toString() {
401: return ClassUtils.getShortClassName(this .getClass())
402: + "[reference=" + getIdentity() + "]";
403: }
404: }
405:
406: //===================================================================
407: // inner class
408: //===================================================================
409: public static class Field extends Image {
410: private final FieldType type;
411: private final Object value;
412:
413: public Field(FieldType type, Object value) {
414: this .type = type;
415: this .value = value;
416: }
417:
418: public void cleanup(boolean reuse) {
419: }
420:
421: void referenceProcessing(Image oldImage) {
422: // nothing to do
423: }
424:
425: /** If a field value has changed return 'true'. */
426: public boolean modified(Image other) {
427: boolean result = false;
428: if (this == other) {
429: result = true;
430: } else {
431: if (other instanceof Field) {
432: result = !type.equals(value, ((Field) other).value);
433: }
434: }
435: return result;
436: }
437:
438: public String toString() {
439: return ClassUtils.getShortClassName(this .getClass())
440: + "[type=" + type + ", value=" + value + "]";
441: }
442: }
443:
444: //===================================================================
445: // inner interface
446: //===================================================================
447: public static interface ImageListener {
448: public void addedOneToOne(ObjectReferenceDescriptor ord,
449: Object refObjOrProxy, Identity oid);
450:
451: public void deletedOneToOne(ObjectReferenceDescriptor ord,
452: Object refObjOrProxy, Identity oid, boolean needsUnlink);
453:
454: public void addedXToN(CollectionDescriptor ord,
455: Object refObjOrProxy, Identity oid);
456:
457: public void deletedXToN(CollectionDescriptor ord,
458: Object refObjOrProxy, Identity oid);
459:
460: public PersistenceBrokerInternal getBroker();
461: }
462:
463: //====================================================
464: // inner class
465: //====================================================
466:
467: /**
468: * Thrown if something unexpected is happen when handling the
469: * object images for state detection.
470: */
471: public static class ImageException extends OJBRuntimeException {
472: public ImageException() {
473: }
474:
475: public ImageException(String msg) {
476: super (msg);
477: }
478:
479: public ImageException(Throwable cause) {
480: super (cause);
481: }
482:
483: public ImageException(String msg, Throwable cause) {
484: super(msg, cause);
485: }
486: }
487: }
|