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>&copy; 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    }