001    package com.sptci.prevayler;
002    
003    import com.sptci.ReflectionUtility;
004    
005    import java.lang.reflect.Field;
006    import java.util.Collection;
007    import java.util.Date;
008    import java.util.LinkedHashSet;
009    
010    /**
011     * Abstracts all the code for decomposing and reconstituting object graphs
012     * for the prevalent system.  Due to the inherent limitations in object
013     * serialisation, this step is necessary to ensure that de-serialised objects
014     * contain proper references to other prevalent objects and not local copies.
015     *
016     * <p>&copy; Copyright 2008 <a href='http://sptci.com/' target='_top'>Sans
017     *   Pareil Technologies, Inc.</a></p>
018     * @author Rakesh Vidyadharan 2008-05-23
019     * @version $Id: ObjectGraphSystem.java 22 2008-11-24 19:04:25Z sptrakesh $
020     */
021    abstract class ObjectGraphSystem extends SearchSystem
022    {
023      private static final long serialVersionUID = 1L;
024    
025      /**
026       * Create a clone of the specified prevalent object and reconstitute object
027       * references to other prevalent objects.  Reads in the references from
028       * {@link #referenceMap} and reconstitutes the references.  Recursively
029       * invokes this method on prevalent objects to ensure that the entire
030       * object graph is replicated.
031       *
032       * @see #populateReference
033       * @param object The object that is to be cloned and reconstituted.
034       * @return The reconstituted prevalent object that represents a persisted
035       *   prevalent object.
036       * @throws PrevalentException If errors are encountered while reconsituting
037       *   the prevalent object.
038       */
039      @SuppressWarnings( {"unchecked"} )
040      protected PrevalentObject compose( final PrevalentObject object )
041          throws PrevalentException
042      {
043        if ( object == null ) return null;
044        if ( getTaskQueue().contains( object ) )
045        {
046          for ( PrevalentObject o : getTaskQueue() )
047          {
048            if ( o.equals( object ) ) return o;
049          }
050        }
051    
052        final PrevalentObject obj = (PrevalentObject) object.clone();
053    
054        try
055        {
056          getTaskQueue().add( obj );
057          populateReference( obj );
058        }
059        catch ( IllegalAccessException iex )
060        {
061          throw new PrevalentException( iex );
062        }
063        finally
064        {
065          getTaskQueue().remove( obj );
066        }
067    
068        return obj;
069      }
070    
071      /**
072       * Populate the references to other prevalent objects in the specified
073       * prevalent object.
074       *
075       * @param object The prevalent object that is being reconstituted.
076       * @throws PrevalentException If errors are encountered while fetching
077       *   the references to the other prevalent objects.
078       * @throws IllegalAccessException If errors are encountered while setting
079       *   the field values.
080       */
081      private void populateReference( final PrevalentObject object )
082          throws PrevalentException, IllegalAccessException
083      {
084        final ReferenceStorage referenceStorage =
085            getReferenceStorage( object.getClass() );
086    
087        for ( String name : referenceStorage.getFields( object ) )
088        {
089          final Field field =
090              ReflectionUtility.fetchField( name, object );
091          final PrimaryStorage primaryStorage =
092            getPrimaryStorage( field.getType() );
093    
094          if ( Collection.class.isAssignableFrom( field.getType() ) )
095          {
096            final Collection<PrevalentObject> objects =
097                new LinkedHashSet<PrevalentObject>();
098            final Collection oids =
099                (Collection) referenceStorage.getValue( object, name );
100    
101            if ( oids != null )
102            {
103              for ( Object id : oids )
104              {
105                PrevalentObject value = primaryStorage.get( id );
106                if ( "com.sptci.prevayler.model.Two".equals( object.getClass().getName() ) &&
107                    "children".equals( field.getName() ) )
108                {
109                  System.out.format( "Referenced object before: %s%n", value );
110                }
111                value = compose( value );
112                if ( "com.sptci.prevayler.model.Two".equals( object.getClass().getName() ) &&
113                    "children".equals( field.getName() ) )
114                {
115                  System.out.format( "Referenced object after: %s%n", value );
116                }
117                if ( value != null ) objects.add( value );
118                if ( "com.sptci.prevayler.model.Two".equals( object.getClass().getName() ) &&
119                    "children".equals( field.getName() ) )
120                {
121                  System.out.format( "Still found child with oid: %s in Two: %s%n", id, object.getObjectId() );
122                }
123              }
124            }
125    
126            field.set( object, objects );
127          }
128          else if ( PrevalentObject.class.isAssignableFrom( field.getType() ) )
129          {
130            final Object oid = referenceStorage.getValue( object, name );
131            final PrevalentObject value = primaryStorage.get( oid );
132            field.set( object, compose( value ) );
133          }
134        }
135      }
136    
137      /**
138       * Clone the specified object and decouple references to other prevalent
139       * object to make suitable for storage in the prevalent system.  Updates
140       * {@link #referenceMap} with references that are decomposed.
141       *
142       * <p>If a referenced persistent object is not yet persistent, then it is
143       * made persistent following persistence by reachability principle.  This
144       * addition is affected by invoked {@link #save} on the referenced object
145       * resulting in persisting the entire object graph through reachability.</p>
146       *
147       * @see #fetch( Class, Object )
148       * @param object The object that is to be cloned and decoupled.
149       * @param executionTime The datetime at which the transaction was executed.
150       * @return The decomposed prevalent object.
151       * @throws PrevalentException If errors are encountered while processing
152       *   the class fields.
153       */
154      @SuppressWarnings( {"unchecked"} )
155      protected PrevalentObject decompose( final PrevalentObject object,
156          final Date executionTime ) throws PrevalentException
157      {
158        final PrevalentObject obj = (PrevalentObject) object.clone();
159    
160        for ( Field field : ReflectionUtility.fetchFields( object ).values() )
161        {
162          if ( PrevalentObject.class.isAssignableFrom( field.getType() ) )
163          {
164            decomposeObject( obj, field, executionTime );
165          }
166          else if ( Collection.class.isAssignableFrom( field.getType() ) )
167          {
168            decomposeCollection( obj, field, executionTime );
169          }
170        }
171    
172    
173        return obj;
174      }
175    
176      /**
177       * Decompose a direct reference represented by the specified field in the
178       * prevalent object.
179       *
180       * @param object The prevalent object to decompose.
181       * @param field The field that contains a direct reference to another
182       *   prevalent object.
183       * @param executionTime The datetime at which the transaction was executed.
184       * @throws PrevalentException If errors are encountered while accessing
185       *   the field.
186       */
187      private void decomposeObject( final PrevalentObject object,
188          final Field field, Date executionTime ) throws PrevalentException
189      {
190        try
191        {
192          final ReferenceStorage referenceStorage =
193              getReferenceStorage( object.getClass() );
194          PrevalentObject po = (PrevalentObject) field.get( object );
195          if ( po == null ) return;
196    
197          if ( ( po.getObjectId() == null ) ||
198              ( fetch( po.getClass(), po.getObjectId() ) == null ) )
199          {
200            if ( ! getTaskQueue().contains( po ) ) save( po, executionTime );
201          }
202    
203          referenceStorage.add( object, field.getName(), po.getObjectId() );
204          field.set( object, null );
205        }
206        catch ( PrevalentException pex )
207        {
208          throw pex;
209        }
210        catch ( Throwable t )
211        {
212          throw new PrevalentException( t );
213        }
214      }
215    
216      /**
217       * Decompose a collection or references to other prevalent objects in the
218       * prevalent object being managed.
219       *
220       * @param object The prevalent object that is to be decomposed prior to
221       *   storage in the system.
222       * @param field The field that contains a collection of references to other
223       *   prevalent objects.
224       * @param executionTime The datetime at which the transaction was executed.
225       * @throws PrevalentException If errors are encountered while fetching the
226       *   fields of the prevalent object.
227       */
228      @SuppressWarnings( {"unchecked"} )
229      private void decomposeCollection( final PrevalentObject object,
230          final Field field, final Date executionTime ) throws PrevalentException
231      {
232        final ReferenceStorage referenceStorage =
233            getReferenceStorage( object.getClass() );
234    
235        try
236        {
237          Collection collection = (Collection) field.get( object );
238          if ( collection == null ) return;
239    
240          // Clone the collection to leave the original prevalent object
241          // collection untouched.
242          if ( collection instanceof Cloneable )
243          {
244            collection = (Collection) ReflectionUtility.execute( collection, "clone" );
245            field.set( object, collection );
246          }
247    
248          final Collection oids = new LinkedHashSet( collection.size() );
249          boolean clearCollection = false;
250    
251          for ( Object obj : collection )
252          {
253            if ( obj instanceof PrevalentObject )
254            {
255              clearCollection = true;
256              PrevalentObject po = (PrevalentObject) obj;
257    
258              if ( ( po.getObjectId() == null ) ||
259                  ( fetch( po.getClass(), po.getObjectId() ) == null ) )
260              {
261                save( po, executionTime );
262              }
263    
264              oids.add( po.getObjectId() );
265            }
266          }
267    
268          if ( clearCollection ) collection.clear();
269          referenceStorage.add( object, field.getName(), oids );
270        }
271        catch ( Throwable t )
272        {
273          throw new PrevalentException( t );
274        }
275      }
276    
277      /**
278       * Replace the prevalent object in the field specified from the specified
279       * <code>object</code> prevalent object to the <code>po</code> object
280       * that exists in the system.
281       *
282       * @see #checkUnique
283       * @param field The field whose value is being updated.
284       * @param object The prevalent object that is being updated.
285       * @param executionTime The datetime at which the transaction was executed.
286       * @throws ConstraintException If the field is marked as unique and the
287       *   newObject specified is already associated with another prevalent
288       *   object of the same type.
289       * @throws PrevalentException If errors are encountered while setting
290       *   the value of the field.
291       */
292      protected void update( final Field field, final PrevalentObject object,
293          final Date executionTime ) throws PrevalentException
294      {
295        final ReferenceStorage referenceStorage = getReferenceStorage( object.getClass() );
296        final Object oid = referenceStorage.getValue( object, field.getName() );
297        final PrimaryStorage primaryStorage = getPrimaryStorage( field.getType() );
298        final IndexStorage indexStorage = getIndexStorage( object.getClass() );
299    
300        try
301        {
302          PrevalentObject source = (PrevalentObject) field.get( object );
303          final PrevalentObject destination = primaryStorage.get( oid );
304    
305          if ( ( source != null ) && ! getTaskQueue().contains( source ) )
306          {
307            source = save( source, executionTime );
308          }
309    
310          if ( ( source == null ) || ! source.equals(  destination ) )
311          {
312            checkUnique( field, object, source );
313    
314            indexStorage.remove( field.getName(), destination, object );
315            referenceStorage.remove( object, field.getName() );
316    
317            if ( source != null )
318            {
319              indexStorage.add( field.getName(), source, object );
320              referenceStorage.add( object, field.getName(), source.getObjectId() );
321            }
322          }
323        }
324        catch ( PrevalentException pex )
325        {
326          throw pex;
327        }
328        catch ( Throwable t )
329        {
330          throw new PrevalentException( t );
331        }
332      }
333    }