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<MyPrevalentObject> save = new Save<MyPrevalentObject>( 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>© 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 }