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    }