001    package com.sptci.prevayler;
002    
003    import org.prevayler.Prevayler;
004    import org.prevayler.PrevaylerFactory;
005    import org.prevayler.foundation.serialization.JavaSerializer;
006    import org.prevayler.foundation.serialization.XStreamSerializer;
007    
008    import java.io.IOException;
009    import java.util.Timer;
010    import java.util.TimerTask;
011    import java.util.concurrent.ConcurrentHashMap;
012    import java.util.concurrent.ConcurrentMap;
013    import java.util.logging.Level;
014    import java.util.logging.Logger;
015    
016    /**
017     * A factory class used to boot-strap {@link PrevalentSystem} instances.
018     *
019     * <p>This class may be configured using the following JVM system properties:</p>
020     * <ol>
021     *   <li><code>sptodb.data.dir</code> - The directory under which the database
022     *     snapshot and journal files are stored.  The default value used if this
023     *     property is not specified is <code>/var/data/sptodb</code>.</li>
024     *   <li><code>sptodb.snapshot.interval</code> - The interval in seconds at
025     *     which snapshots of the prevalent system are to be taken.  The default
026     *     value used is <code>86400</code> (one day).</li>
027     *   <li><code>sptodb.serialiser.format</code> - The format to use for taking
028     *     snapshots of the prevalent system and creating transaction journals.
029     *     The supported options are:
030     *     <ol>
031     *       <li><code>java</code> - Indicates that regular Java object
032     *         serialisation be used to take the snapshot and write journals.
033     *           This is the default unless otherwise specified.</li>
034     *       <li><code>xml</code> - Indicates that the journals should be written
035     *         and snapshot taken using
036     *         <a href='http://xstream.codehaus.org/'>XStream</a>.  XML
037     *         serialisation is slower (both serialisation and de-serialisation),
038     *         however gives you more options processing the prevalent system
039     *         for other purposes.  Also useful if you need to restore the
040     *         system after heavy modifications (refactoring) to the object
041     *         model.</li>
042     *     </ol>
043     * </ol>
044     *
045     * <p>The following code shows sample usage of this class</p>
046     * <pre>
047     *   import com.sptci.prevayler.PrevalentSystemFactory;
048     *   import com.sptci.prevayler.transaction.Save;
049     *
050     *   ...
051     *     // MyPrevalentObject is a sub-class of PrevalentObject
052     *     final MyPrevalentObject obj1 = new MyPrevalentObject();
053     *     obj1.setXXX();
054     *     ...
055     *     final Save&lt;MyPrevalentObject&gt; save = new Save&lt;MyPrevalentObject&gt;( obj1 );
056     *     final MyPrevalentObject obj2 = PrevalentSystemFactory.getPrevayler().execute( save );
057     *     System.out.format( "MyPrevalentObject created with OID: %s%n", obj2.getObjectId() );
058     * </pre>
059     *
060     * @see PrevalentManager
061     * <p>&copy; Copyright 2008 <a href='http://sptci.com/' target='_top'>Sans Pareil Technologies, Inc.</a></p>
062     * @author Rakesh Vidyadharan 2008-05-22
063     * @version $Id: PrevalentSystemFactory.java 23 2008-11-24 19:49:55Z sptrakesh $
064     */
065    public final class PrevalentSystemFactory
066    {
067      /**
068       * The system parameter used to configure the prevalent data directory.
069       *
070       * {@value}
071       */
072      public static final String DATA_DIRECTORY = "sptodb.data.dir";
073    
074      /**
075       * The default value to use for the prevalent data directory.
076       *
077       * {@value}
078       */
079      public static final String DEFAULT_DIRECTORY = "/var/data/sptodb";
080    
081      /**
082       * The directory under {@link #DATA_DIRECTORY} in which the prevalent
083       * objects are stored.
084       *
085       * {@value}
086       */
087      public static final String OBJECT_STORAGE = "data";
088    
089      /**
090       * The directory under {@link #DATA_DIRECTORY} under which lucene search
091       * indices are stored.
092       *
093       * {@value}
094       */
095      public static final String SEARCH_STORAGE = "search";
096    
097      /**
098       * The system property used to configure the interval at which a snapshot
099       * of the database is taken.  Note that the value should be specified in
100       * <b>seconds</b>.
101       *
102       * {@value}
103       */
104      public static final String SNAPSHOT_INTERVAL = "sptodb.snapshot.interval";
105    
106      /**
107       * The default snapshot interval to use.  Default is 24 hours.
108       *
109       * {@value}
110       */
111      public static final String DEFAULT_SNAPSHOT_INTERVAL = "86400";
112    
113      /**
114       * The JVM system property used to configure the serialisation technique
115       * used for snapshots and transaction journals.
116       *
117       * {@value}
118       */
119      public static final String SERIALISER_FORMAT = "sptodb.serialiser.format";
120    
121      /**
122       * The default value for the {@link #SERIALISER_FORMAT} property.  Defaults
123       * to Java serialisation.
124       *
125       * {@value}
126       */
127      public static final String DEFAULT_SERIALISER_FORMAT = "java";
128    
129      /**
130       * The JVM system property used to specify the size of the batches in
131       * which the search index writer is to be committed.
132       */
133      public static final String SEARCH_BATCH_SIZE = "sptodb.search.batchSize";
134    
135      /**
136       * The default value for the {@link #SEARCH_BATCH_SIZE} property.
137       *
138       * {@value}
139       */
140      public static final String DEFAULT_SEARCH_BATCH_SIZE = "20";
141    
142      /** The logger to use to log messages. */
143      private static final Logger logger = Logger.getLogger( "SPTODBLogger" );
144    
145      /**
146       * A map used to maintain the various prevalent systems maintained by the
147       * factory.
148       */
149      private static final ConcurrentMap<Class,Prevayler> systems =
150          new ConcurrentHashMap<Class,Prevayler>();
151    
152      /** Default constructor.  Cannot be instantiated. */
153      private PrevalentSystemFactory() {}
154    
155      /**
156       * Boot-strap a prevalent system using the default {@link PrevalentSystem}
157       * class.
158       *
159       * @see #getPrevayler( Class )
160       * @return The initialised prevayler instance to use.
161       * @throws PrevalentException If errors are encountered while boot strapping
162       *   the prevalent system.
163       */
164      public static Prevayler getPrevayler()
165        throws PrevalentException
166      {
167        return getPrevayler( PrevalentSystem.class );
168      }
169    
170      /**
171       * Create a prevalent system for the specified system class.
172       *
173       * @see #getPrevayler( Class, String )
174       * @param system The class that represents the prevalent system to be
175       *   managed.
176       * @return The initialised prevayler instance to use.
177       * @throws PrevalentException If errors are encountered while boot strapping
178       *   the prevalent system.
179       */
180      public static Prevayler getPrevayler( final Class system )
181        throws PrevalentException
182      {
183        return getPrevayler( system, getDatabaseDirectory( system ) );
184      }
185    
186      /**
187       * Create a prevalent system for the specified system class.
188       *
189       * @see #getPrevayler( Class, String, String )
190       * @param system The class that represents the prevalent system to be
191       *   managed.
192       * @param directory The directory in which serialised state of the
193       *   prevalent system is to be stored.  Note that you must specify different
194       *   directories if you are using this factory to boot-strap multiple
195       *   prevalent system instances.
196       * @return The initialised prevayler instance to use.
197       * @throws PrevalentException If errors are encountered while boot strapping
198       *   the prevalent system.  Also thrown if the <code>system</code> specified
199       *   is not a sub-class of {@link PrevalentSystem}.
200       */
201      public static Prevayler getPrevayler( final Class system,
202          final String directory ) throws PrevalentException
203      {
204        final String format =
205                System.getProperty( SERIALISER_FORMAT, DEFAULT_SERIALISER_FORMAT );
206        return getPrevayler( system, directory, format );
207      }
208    
209      /**
210       * Create a prevalent system for the specified system class.
211       *
212       * @see #snapshot
213       * @param system The class that represents the prevalent system to be
214       *   managed.
215       * @param directory The directory in which serialised state of the
216       *   prevalent system is to be stored.  Note that you must specify different
217       *   directories if you are using this factory to boot-strap multiple
218       *   prevalent system instances.
219       * @param serialiser The serialiser to use for the transaction journals and
220       *   snapshots.  Valid values are <code>java</code> or <code>xml</code>.
221       * @return The initialised prevayler instance to use.
222       * @throws PrevalentException If errors are encountered while boot strapping
223       *   the prevalent system.  Also thrown if the <code>system</code> specified
224       *   is not a sub-class of {@link PrevalentSystem}.
225       */
226      public static Prevayler getPrevayler( final Class system,
227          final String directory, final String serialiser ) throws PrevalentException
228      {
229        if ( ! systems.containsKey( system ) )
230        {
231          if ( ! PrevalentSystem.class.isAssignableFrom( system ) )
232          {
233            throw new PrevalentException( "Class specified: " + system +
234                " is not a sub-class of " + PrevalentSystem.class.getName() );
235          }
236    
237          final long start = System.currentTimeMillis();
238    
239          try
240          {
241            final PrevaylerFactory factory = new PrevaylerFactory();
242            factory.configurePrevalenceDirectory( directory );
243    
244            if ( DEFAULT_SERIALISER_FORMAT.equalsIgnoreCase( serialiser ) )
245            {
246              factory.configureSnapshotSerializer( new JavaSerializer() );
247              factory.configureJournalSerializer( new JavaSerializer() );
248            }
249            else
250            {
251              factory.configureJournalSerializer( new XStreamSerializer( "UTF-8" ) );
252              factory.configureSnapshotSerializer( new XStreamSerializer( "UTF-8" ) );
253            }
254    
255            factory.configurePrevalentSystem( system.newInstance() );
256    
257            final Prevayler prevayler = factory.create();
258            snapshot( prevayler );
259            systems.putIfAbsent( system, prevayler );
260          }
261          catch ( Throwable t )
262          {
263            throw new PrevalentException( t );
264          }
265    
266          final long end = System.currentTimeMillis();
267          logger.info( "Initialised prevalent system of type: " + system +
268              " in " + ( ( end - start ) / 1000.0 ) + " seconds." );
269        }
270    
271        return systems.get( system );
272      }
273    
274      /**
275       * Return the root directory under which the entire database system is
276       * stored.  Note that this is the value of the {@link #DATA_DIRECTORY}
277       * as configured or the default value.
278       *
279       * @return The configured directory or the default value.
280       */
281      protected static String getDataDirectory()
282      {
283        return System.getProperty( DATA_DIRECTORY, DEFAULT_DIRECTORY );
284      }
285    
286      /**
287       * Return the directory under which serialised instances of the specified
288       * class are to be stored.
289       *
290       * @param system The class whose instances are to be stored.
291       * @return The directory under which the instances are to be serialised.
292       */
293      protected static String getDatabaseDirectory( final Class system )
294      {
295        final String separator = System.getProperty( "file.separator" );
296        final String directory = getDataDirectory();
297        return ( directory + separator + OBJECT_STORAGE +
298            separator + system.getName() );
299      }
300    
301      /**
302       * Return the directory under which the lucene full-text search indices
303       * are to be stored.
304       *
305       * @param system The class whose search index location is to be returned
306       * @return The directory under which the search indices are stored.
307       */
308      protected static String getSearchDirectory( final Class system )
309      {
310        final String separator = System.getProperty( "file.separator" );
311        final String directory = getDataDirectory();
312        return ( directory + separator + SEARCH_STORAGE +
313            separator + system.getName() );
314      }
315    
316      /**
317       * Return the size of the batch at which lucene index writer is to be
318       * committed.
319       *
320       * @return The number of transactions after which the index writer is to
321       *   be committed and index reader re-opened.
322       */
323      protected static int getSearchBatchSize()
324      {
325        return Integer.parseInt(
326            System.getProperty( SEARCH_BATCH_SIZE, DEFAULT_SEARCH_BATCH_SIZE ) );
327      }
328    
329      /**
330       * Start a timer task for taking snapshots of the prevalent system.
331       * This method will be enhanced to take snapshots at configured intervals.
332       *
333       * @param prevayler The prevalent system to snapshot.
334       */
335      private static void snapshot( final Prevayler prevayler )
336      {
337        final long interval = Long.parseLong( System.getProperty(
338            SNAPSHOT_INTERVAL, DEFAULT_SNAPSHOT_INTERVAL ) ) * 1000;
339        final TimerTask task = new SnapshotTask( prevayler );
340    
341        logger.info(
342            "Scheduling prevalent system snapshot at interval " + interval );
343        ( new Timer( true ) ).scheduleAtFixedRate( task, interval, interval );
344      }
345    
346      /**
347       * A {@link java.util.TimerTask} that is used to snapshot the {@link
348       * #prevayler} periodically.
349       */
350      private static class SnapshotTask extends TimerTask
351      {
352        /** The prevalent system to snapshot. */
353        private final Prevayler prevayler;
354    
355        /**
356         *  Create a new instance of the task for the specified system.
357         *
358         * @param prevayler The prevayler instance to snapshot.
359         */
360        private SnapshotTask( final Prevayler prevayler )
361        {
362          this.prevayler = prevayler;
363        }
364    
365        /**
366         * The action to be performed by this task when run by a {@link
367         * java.util.Timer}.
368         */
369        public void run()
370        {
371          logger.info( "Taking snapshot of prevalent system" );
372          try
373          {
374            prevayler.takeSnapshot();
375          }
376          catch ( IOException ioex )
377          {
378            logger.log( Level.WARNING,
379                "Error taking prevalent system snapshot", ioex );
380          }
381        }
382      }
383    }