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>© 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 }