View Javadoc

1   package org.apache.ojb.odmg;
2   
3   /* Copyright 2002-2005 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.commons.lang.ClassUtils;
27  import org.apache.ojb.broker.Identity;
28  import org.apache.ojb.broker.IdentityFactory;
29  import org.apache.ojb.broker.OJBRuntimeException;
30  import org.apache.ojb.broker.PersistenceBrokerInternal;
31  import org.apache.ojb.broker.core.proxy.CollectionProxy;
32  import org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl;
33  import org.apache.ojb.broker.core.proxy.CollectionProxyListener;
34  import org.apache.ojb.broker.core.proxy.ProxyHelper;
35  import org.apache.ojb.broker.metadata.CollectionDescriptor;
36  import org.apache.ojb.broker.metadata.FieldType;
37  import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
38  import org.apache.ojb.broker.util.BrokerHelper;
39  import org.apache.ojb.broker.util.logging.Logger;
40  import org.apache.ojb.broker.util.logging.LoggerFactory;
41  
42  /**
43   * This class encapsulates classes used to take persistence capable object
44   * state snapshoot and to detect changed fields or references.
45   *
46   * @version $Id: Image.java,v 1.1 2007-08-24 22:17:37 ewestfal Exp $
47   */
48  public abstract class Image
49  {
50      static Logger log = LoggerFactory.getLogger(Image.class);
51      private long timestamp = System.currentTimeMillis();
52  
53      private Image()
54      {
55      }
56  
57      boolean illegalImageComparison(Image oldImage)
58      {
59          return timestamp < oldImage.timestamp;
60      }
61  
62      public abstract void cleanup(boolean reuse);
63  
64      public abstract boolean modified(Image other);
65  
66      abstract void referenceProcessing(Image oldImage);
67  
68      public void performReferenceDetection(Image oldImage)
69      {
70          if(illegalImageComparison(oldImage))
71          {
72              throw new ImageException("The specified Image object is newer than current one, wrong Image order!");
73          }
74          referenceProcessing(oldImage);
75      }
76  
77      //===================================================================
78      // inner class
79      //===================================================================
80      public static class MultipleRef extends Image implements CollectionProxyListener
81      {
82          static final int IS_NORMAL_OBJECT = 11;
83          static final int IS_MATERIALIZED_PROXY = 13;
84          static final int IS_UNMATERIALIZED_PROXY = 17;
85  
86          private ImageListener listener;
87          private final CollectionDescriptor cod;
88          private final Object collectionOrArray;
89          private Map references;
90          private int status;
91          private boolean hasTransientIdentity;
92          private boolean isRefreshed;
93  
94          public MultipleRef(ImageListener listener, CollectionDescriptor cod, Object collectionOrArray)
95          {
96              this.listener = listener;
97              this.cod = cod;
98              this.collectionOrArray = collectionOrArray;
99              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 }