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