001 package com.sptci.prevayler;
002
003 import com.sptci.ReflectionUtility;
004 import org.apache.lucene.search.Filter;
005 import org.apache.lucene.search.Query;
006 import org.apache.lucene.search.Sort;
007
008 import java.lang.reflect.Field;
009 import java.lang.reflect.InvocationTargetException;
010 import java.util.ArrayList;
011 import java.util.Collection;
012 import java.util.Date;
013 import java.util.LinkedHashSet;
014 import java.util.List;
015 import java.util.Map;
016 import java.util.logging.Level;
017
018 /**
019 * A base class for prevalent systems that will be managed by Prevayler and
020 * wrapped in a {@link org.prevayler.Prevayler} instance. This class may
021 * be sub-classed to extend features or customise the implementation of the
022 * various methods used to implement the rules of the database engine. We
023 * hope that you will not need to sub-class this instance.
024 *
025 * <p>Note that all the methods provided by this class ensure
026 * encapsulation of the data stored in the prevalent system. All objects
027 * stored are clones of the prevalent objects passed in, and all prevalent
028 * object instances returned by transactional or query methods are clones of
029 * the instances stored in the system. This does increase the memory
030 * footprint of the system, however it is more critical to preserve
031 * encapsulation of data. To improve performance client code may utilise
032 * various caching products to cache the results of query execution to reduce
033 * the number of copies of the prevalent objects in the JVM heap space.</p>.
034 *
035 * <p>© Copyright 2008 <a href='http://sptci.com/' target='_top'>Sans
036 * Pareil Technologies, Inc.</a></p>
037 * @see PrevalentSystemFactory
038 * @author Rakesh Vidyadharan 2008-05-22
039 * @version $Id: PrevalentSystem.java 22 2008-11-24 19:04:25Z sptrakesh $
040 */
041 public class PrevalentSystem extends ObjectGraphSystem
042 {
043 private static final long serialVersionUID = 2L;
044
045 /** The name of the object id field in {@link PrevalentObject}. */
046 private static final String OBJECT_ID = "objectId";
047
048 /** The name of the meta data field in {@link PrevalentObject}. */
049 private static final String META_DATA = "_sptodbMetaData";
050
051 /** Default constructor. */
052 protected PrevalentSystem() {}
053
054 /** {@inheritDoc} */
055 public PrevalentObject save( final PrevalentObject object,
056 final Date executionTime ) throws PrevalentException
057 {
058 return ( ( object.isPersistent() ) ? update( object, executionTime ) :
059 add( object, executionTime ) );
060 }
061
062 /** {@inheritDoc} */
063 public PrevalentObject delete( final PrevalentObject object,
064 final Date executionTime ) throws PrevalentException
065 {
066 preDelete( object, executionTime );
067 final Object objectId = object.getObjectId();
068
069 if ( getTaskQueue().contains( object ) ) return object;
070
071 try
072 {
073 getTaskQueue().add( object );
074
075 final PrimaryStorage primaryStorage = getPrimaryStorage( object.getClass() );
076 primaryStorage.remove( object );
077
078 remove( object );
079
080 getTaskQueue().remove( object );
081 final Field field = ReflectionUtility.fetchField( OBJECT_ID, object );
082 field.set( object, null );
083 object.set_sptodbMetaData( null );
084 }
085 catch ( Throwable t )
086 {
087 logger.log( Level.SEVERE, "Error setting objectId: " + objectId +
088 " to null for class: " + object.getClass().getName(), t );
089 }
090 finally
091 {
092 getTaskQueue().remove( object );
093 }
094
095 return object;
096 }
097
098 /** {@inheritDoc} */
099 public int count( final Class cls )
100 {
101 final PrimaryStorage primaryStorage = getPrimaryStorage( cls );
102 return primaryStorage.size();
103 }
104
105 /**
106 * Fetch the prevalent object of the specified type with the specified
107 * object id from the prevalent system.
108 *
109 * @see #compose
110 * @param cls The type of the prevalent object.
111 * @param oid The object id for the prevalent object to retrieve.
112 * @return The prevalent object instance. Returns <code>null</code> if no
113 * such object is stored in the prevalent system.
114 * @throws PrevalentException If errors are encountered while reconstituting
115 * the prevalent object.
116 */
117 public PrevalentObject fetch( final Class cls, final Object oid )
118 throws PrevalentException
119 {
120 final PrimaryStorage primaryStorage = getPrimaryStorage( cls );
121 return compose( primaryStorage.get( oid ) );
122 }
123
124 /** {@inheritDoc} */
125 public Collection<PrevalentObject> fetch( final Class cls,
126 final String field, final Object object )
127 throws PrevalentException
128 {
129 final Collection<PrevalentObject> results =
130 new LinkedHashSet<PrevalentObject>();
131
132 final IndexStorage indexStorage = getIndexStorage( cls );
133 final Collection<IndexedObject> collection =
134 indexStorage.get( field, object );
135
136 for ( IndexedObject io : collection )
137 {
138 final PrevalentObject po = fetch( io.type, io.objectId );
139 results.add( po );
140 }
141
142 return results;
143 }
144
145 /** {@inheritDoc} */
146 public Collection<PrevalentObject> fetchUnion( final Class cls,
147 final Map<String,?> parameters ) throws PrevalentException
148 {
149 final Collection<PrevalentObject> results =
150 new LinkedHashSet<PrevalentObject>();
151
152 for ( Map.Entry<String,?> entry : parameters.entrySet() )
153 {
154 results.addAll( fetch( cls, entry.getKey(), entry.getValue() ) );
155 }
156
157 return results;
158 }
159
160 /** {@inheritDoc} */
161 public Collection<PrevalentObject> fetchIntersection( final Class cls,
162 final Map<String,?> parameters ) throws PrevalentException
163 {
164 final Collection<PrevalentObject> results =
165 new LinkedHashSet<PrevalentObject>();
166
167 List<Collection<PrevalentObject>> matches =
168 new ArrayList<Collection<PrevalentObject>>( parameters.size() );
169
170 for ( Map.Entry<String,?> entry : parameters.entrySet() )
171 {
172 final Collection<PrevalentObject> coll =
173 fetch( cls, entry.getKey(), entry.getValue() );
174 matches.add( coll );
175 }
176
177 for ( int i = 0; i < matches.size(); ++i )
178 {
179 for ( PrevalentObject po : matches.get( i ) )
180 {
181 boolean add = true;
182
183 for ( int j = 0; j < matches.size(); ++j )
184 {
185 if ( i != j )
186 {
187 if ( ! matches.get( j ).contains( po ) )
188 {
189 add = false;
190 }
191 }
192 }
193
194 if ( add ) results.add( po );
195 }
196 }
197
198 return results;
199 }
200
201 /** {@inheritDoc} */
202 public Collection<PrevalentObject> fetch( final Class cls,
203 final long start, final long end ) throws PrevalentException
204 {
205 final Collection<PrevalentObject> results =
206 new LinkedHashSet<PrevalentObject>( (int) ( end - start ) );
207
208 final PrimaryStorage primaryStorage = getPrimaryStorage( cls );
209
210 for ( PrevalentObject obj : primaryStorage.get( start, end ) )
211 {
212 results.add( compose( obj ) );
213 }
214
215 return results;
216 }
217
218 /** {@inheritDoc} */
219 public Collection<PrevalentObject> search( final Query query,
220 final Filter filter, final int count, final Sort sort )
221 throws PrevalentException
222 {
223 Collection<PrevalentObject> collection =
224 new LinkedHashSet<PrevalentObject>( count );
225
226 try
227 {
228 search( query, filter, count, sort, collection );
229 }
230 catch ( PrevalentException e )
231 {
232 throw e;
233 }
234 catch ( Throwable t )
235 {
236 throw new PrevalentException( t );
237 }
238
239 return collection;
240 }
241
242 /**
243 * Add a new prevalent object to the prevalent system. It is recommended
244 * that you over-ride the methods invoked by this method rather than this
245 * method itself.
246 *
247 * @see #preAdd
248 * @see #getPrimaryStorage
249 * @see #decompose
250 * @see #setOid
251 * @see #index
252 * @param object The object to be added to the system.
253 * @param executionTime The time at which the transaction was executed.
254 * @return The potentially modified <code>object</code> passed in.
255 * @throws PrevalentException If errors are encountered while adding the
256 * object.
257 */
258 protected PrevalentObject add( final PrevalentObject object,
259 final Date executionTime ) throws PrevalentException
260 {
261 if ( object == null ) return null;
262 if ( getTaskQueue().contains( object ) ) return object;
263
264 preAdd( object );
265
266 final PrimaryStorage primaryStorage =
267 getPrimaryStorage( object.getClass() );
268 setOid( object );
269 object.set_sptodbMetaData( new MetaData( executionTime.getTime() ) );
270
271 try
272 {
273 getTaskQueue().add( object );
274 final PrevalentObject obj = decompose( object, executionTime );
275
276 primaryStorage.add( obj );
277 index( obj );
278 }
279 finally
280 {
281 getTaskQueue().remove( object );
282 }
283
284 return object;
285 }
286
287 /**
288 * Update the specified prevalent object in the prevalent system. If the
289 * specified object does not represent a persistent instance, it is added
290 * to the system. In this regard it is possible to directly use this method
291 * always instead of {@link #add}.
292 *
293 * <p>Note that this method invokes itself recursively to update any objects
294 * in the object graph that represent prevalent objects that also need
295 * updating.</p>
296 *
297 * @see #fetch( Class, Object )
298 * @see #add
299 * @see #update( Field, PrevalentObject, Date )
300 * @param object The prevalent object to update in the system.
301 * @param executionTime The datetime at which the transaction was executed.
302 * @return The potentially modified prevalent object. The returned object
303 * is modified only if the object is added to the system and when datastore
304 * object id is in use.
305 * @throws PrevalentException If errors are encountered while updating
306 * (or adding) the prevalent object.
307 */
308 protected PrevalentObject update( final PrevalentObject object,
309 final Date executionTime ) throws PrevalentException
310 {
311 if ( object == null ) return null;
312 if ( getTaskQueue().contains( object ) ) return object;
313
314 final PrimaryStorage primaryStorage = getPrimaryStorage( object.getClass() );
315 final PrevalentObject po = primaryStorage.get( object.getObjectId() );
316
317 try
318 {
319 getTaskQueue().add( object );
320
321 for ( Field field : ReflectionUtility.fetchFields( object ).values() )
322 {
323 final Object source = field.get( object );
324 final Object destination = field.get( po );
325
326 if ( PrevalentObject.class.isAssignableFrom( field.getType() ) )
327 {
328 update( field, object, executionTime );
329 }
330 else if ( source instanceof Collection )
331 {
332 boolean prevalentObject = false;
333 for ( Object obj : (Collection) source )
334 {
335 if ( obj instanceof PrevalentObject )
336 {
337 prevalentObject = true;
338 }
339
340 break;
341 }
342
343 if ( prevalentObject )
344 {
345 // special handling
346 }
347 else
348 {
349 field.set( po, ReflectionUtility.execute( source, "clone" ) );
350 }
351 }
352 else
353 {
354 updateOrdinaryField( po, field, source, destination );
355 }
356 }
357
358 object.get_sptodbMetaData().modified = executionTime.getTime();
359 po.get_sptodbMetaData().modified = executionTime.getTime();
360 }
361 catch ( PrevalentException pex )
362 {
363 throw pex;
364 }
365 catch ( Throwable t )
366 {
367 throw new PrevalentException( t );
368 }
369 finally
370 {
371 getTaskQueue().remove( object );
372 }
373
374 return object;
375 }
376
377 /**
378 * Set the {@link PrevalentObject#objectId} field to a new value if
379 * not already set.
380 *
381 * @see #generateOid
382 * @param object The prevalent object whose primary key is to be set.
383 * @throws PrevalentException If errors are encountered while setting the
384 * privary key field.
385 */
386 protected void setOid( final PrevalentObject object )
387 throws PrevalentException
388 {
389 final Object oid = generateOid( object );
390
391 try
392 {
393 final Field field = ReflectionUtility.fetchField( OBJECT_ID, object );
394 field.set( object, oid );
395 }
396 catch ( Throwable t )
397 {
398 throw new PrevalentException( t );
399 }
400 }
401
402 /**
403 * Update an ordinary field (non prevalent object or collection). De-index
404 * and re-index the field value if necessary.
405 *
406 * @param prevalentObject The prevalent object that is being updated.
407 * @param field The field in the prevalent object that is being updated.
408 * @param source The new value that is being set.
409 * @param destination The old value in the field.
410 * @throws IllegalAccessException Reflection error while setting field.
411 * @throws InvocationTargetException Reflection error while setting field.
412 */
413 protected void updateOrdinaryField( final PrevalentObject prevalentObject,
414 final Field field, final Object source, final Object destination )
415 throws IllegalAccessException, InvocationTargetException
416 {
417 if ( OBJECT_ID.equals( field.getName() ) ) return;
418 if ( META_DATA.equals( field.getName() ) ) return;
419 if ( "serialVersionUID".equals( field.getName() ) ) return;
420
421 final IndexStorage indexStorage = getIndexStorage( prevalentObject.getClass() );
422
423 if ( source == null )
424 {
425 field.set( prevalentObject, source );
426 indexStorage.remove( field.getName(), destination, prevalentObject );
427 }
428 else if ( ! source.equals( destination ) )
429 {
430 if ( source instanceof Cloneable )
431 {
432 field.set( prevalentObject, ReflectionUtility.execute( source, "clone" ) );
433 }
434 else
435 {
436 field.set( prevalentObject, source );
437 }
438
439 if ( indexStorage.isFieldIndexed( field.getName() ) )
440 {
441 indexStorage.remove( field.getName(), destination, prevalentObject );
442 indexStorage.add( field.getName(), source, prevalentObject );
443 }
444 }
445 }
446 }