//Prevayler(TM) - The Free-Software Prevalence Layer. //Copyright (C) 2001-2004 Klaus Wuestefeld //This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. //Contributions: Aleksey Aristov, Carlos Villela, Justin Sampson. package org.prevayler; import org.prevayler.foundation.monitor.Monitor; import org.prevayler.foundation.monitor.SimpleMonitor; import org.prevayler.foundation.network.OldNetwork; import org.prevayler.foundation.network.OldNetworkImpl; import org.prevayler.foundation.serialization.JavaSerializer; import org.prevayler.foundation.serialization.Serializer; import org.prevayler.foundation.serialization.SkaringaSerializer; import org.prevayler.foundation.serialization.XStreamSerializer; import org.prevayler.implementation.PrevaylerDirectory; import org.prevayler.implementation.PrevaylerImpl; import org.prevayler.implementation.clock.MachineClock; import org.prevayler.implementation.journal.Journal; import org.prevayler.implementation.journal.PersistentJournal; import org.prevayler.implementation.journal.TransientJournal; import org.prevayler.implementation.publishing.CentralPublisher; import org.prevayler.implementation.publishing.TransactionPublisher; import org.prevayler.implementation.publishing.censorship.LiberalTransactionCensor; import org.prevayler.implementation.publishing.censorship.StrictTransactionCensor; import org.prevayler.implementation.publishing.censorship.TransactionCensor; import org.prevayler.implementation.replication.ClientPublisher; import org.prevayler.implementation.replication.ServerListener; import org.prevayler.implementation.snapshot.GenericSnapshotManager; import org.prevayler.implementation.snapshot.NullSnapshotManager; import java.io.IOException; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * Provides easy access to all Prevayler configurations and implementations * available in this distribution. Static methods are also provided as * short-cuts for the most common configurations. <br> * By default, the Prevayler instances created by this class will write their * Transactions to .journal files before executing them. The * FileDescriptor.sync() method is called to make sure the Java file * write-buffers have been written to the operating system. Many operating * systems, including most recent versions of Linux and Windows, allow the * hard-drive's write-cache to be disabled. This guarantees no executed * Transaction will be lost in the event of a power shortage, for example. <br> * Also by default, the Prevayler instances created by this class will filter * out all Transactions that would throw a RuntimeException or Error if executed * on the Prevalent System. This requires enough RAM to hold another copy of the * prevalent system. * * @see Prevayler */ public class PrevaylerFactory { private Object _prevalentSystem; private Clock _clock; private boolean _transactionFiltering = true; private boolean _transientMode; private String _prevalenceDirectory; private NullSnapshotManager _nullSnapshotManager; private long _journalSizeThreshold; private long _journalAgeThreshold; private OldNetwork _network; private int _serverPort = -1; private String _remoteServerIpAddress; private int _remoteServerPort; public static final int DEFAULT_REPLICATION_PORT = 8756; private Monitor _monitor; private Serializer _journalSerializer; private String _journalSuffix; private Map _snapshotSerializers = new HashMap(); private String _primarySnapshotSuffix; /** * Creates a Prevayler that will use a directory called "PrevalenceBase" * under the current directory to read and write its .snapshot and .journal * files. * * @param newPrevalentSystem * The newly started, "empty" prevalent system that will be used * as a starting point for every system startup, until the first * snapshot is taken. */ public static Prevayler createPrevayler(Serializable newPrevalentSystem) throws IOException, ClassNotFoundException { return createPrevayler(newPrevalentSystem, "PrevalenceBase"); } /** * Creates a Prevayler that will use the given prevalenceBase directory to * read and write its .snapshot and .journal files. * * @param newPrevalentSystem * The newly started, "empty" prevalent system that will be used * as a starting point for every system startup, until the first * snapshot is taken. * @param prevalenceBase * The directory where the .snapshot files and .journal files * will be read and written. */ public static Prevayler createPrevayler(Serializable newPrevalentSystem, String prevalenceBase) throws IOException, ClassNotFoundException { PrevaylerFactory factory = new PrevaylerFactory(); factory.configurePrevalentSystem(newPrevalentSystem); factory.configurePrevalenceDirectory(prevalenceBase); return factory.create(); } /** * Creates a Prevayler that will execute Transactions WITHOUT writing them * to disk. This is useful for running automated tests or demos MUCH faster * than with a regular Prevayler. * * Attempts to take snapshots on this transient Prevayler will throw an * IOException. * * @param newPrevalentSystem * The newly started, "empty" prevalent system. * @see #createCheckpointPrevayler(Serializable newPrevalentSystem, String * snapshotDirectory) */ public static Prevayler createTransientPrevayler( Serializable newPrevalentSystem) { PrevaylerFactory factory = new PrevaylerFactory(); factory.configurePrevalentSystem(newPrevalentSystem); factory.configureNullSnapshotManager(new NullSnapshotManager( newPrevalentSystem, "Transient Prevaylers are unable to take snapshots.")); factory.configureTransientMode(true); try { return factory.create(); } catch (Exception e) { e.printStackTrace(); // Transient Prevayler creation should not // fail. return null; } } /** * @deprecated Use createCheckpointPrevayler() instead of this method. * Deprecated since Prevayler2.00.001. */ public static Prevayler createTransientPrevayler( Serializable newPrevalentSystem, String snapshotDirectory) { return createCheckpointPrevayler(newPrevalentSystem, snapshotDirectory); } /** * Creates a Prevayler that will execute Transactions WITHOUT writing them * to disk. Snapshots will work as "checkpoints" for the system, therefore. * This is useful for stand-alone applications that have a "Save" button, * for example. * * @param newPrevalentSystem * The newly started, "empty" prevalent system that will be used * as a starting point for every system startup, until the first * snapshot is taken. * @param snapshotDirectory * The directory where the .snapshot files will be read and * written. */ public static Prevayler createCheckpointPrevayler( Serializable newPrevalentSystem, String snapshotDirectory) { PrevaylerFactory factory = new PrevaylerFactory(); factory.configurePrevalentSystem(newPrevalentSystem); factory.configurePrevalenceDirectory(snapshotDirectory); factory.configureTransientMode(true); try { return factory.create(); } catch (Exception e) { e.printStackTrace(); // Transient Prevayler creation should not // fail. return null; } } private Clock clock() { return _clock != null ? _clock : new MachineClock(); } /** * Assigns a monitor object to receive notifications from Prevayler. This is * useful for logging or sending eMails to system administrators, for * example. If this method is not called or if null is passed as a * parameter, a SimpleMonitor will be used to log notification on * System.err. * * @param monitor * the Monitor implementation to use. * @see org.prevayler.foundation.monitor.SimpleMonitor */ public void configureMonitor(Monitor monitor) { _monitor = monitor; } /** * Determines whether the Prevayler created by this factory should be * transient (transientMode = true) or persistent (transientMode = false). * Default is persistent. A transient Prevayler will execute its * Transactions WITHOUT writing them to disk. This is useful for stand-alone * applications which have a "Save" button, for example, or for running * automated tests MUCH faster than with a persistent Prevayler. */ public void configureTransientMode(boolean transientMode) { _transientMode = transientMode; } /** * Configures the Clock that will be used by the created Prevayler. The * Clock interface can be implemented by the application if it requires * Prevayler to use a special time source other than the machine clock * (default). */ public void configureClock(Clock clock) { _clock = clock; } /** * Configures the directory where the created Prevayler will read and write * its .journal and .snapshot files. The default is a directory called * "PrevalenceBase" under the current directory. * * @param prevalenceDirectory * Will be ignored for the .snapshot files if a SnapshotManager * is configured. */ public void configurePrevalenceDirectory(String prevalenceDirectory) { _prevalenceDirectory = prevalenceDirectory; } /** * Configures the prevalent system that will be used by the Prevayler * created by this factory. * * @param newPrevalentSystem * If the default Serializer is used, this prevalentSystem must * be Serializable. If another Serializer is used, this * prevalentSystem must be compatible with it. * @see #configureSnapshotSerializer(String,Serializer) */ public void configurePrevalentSystem(Object newPrevalentSystem) { _prevalentSystem = newPrevalentSystem; } /** * Reserved for future implementation. */ public void configureReplicationClient(String remoteServerIpAddress, int remoteServerPort) { _remoteServerIpAddress = remoteServerIpAddress; _remoteServerPort = remoteServerPort; } /** * Reserved for future implementation. */ public void configureReplicationServer(int port) { _serverPort = port; } private void configureNullSnapshotManager( NullSnapshotManager snapshotManager) { _nullSnapshotManager = snapshotManager; } /** * Determines whether the Prevayler created by this factory should filter * out all Transactions that would throw a RuntimeException or Error if * executed on the Prevalent System (default is true). This requires enough * RAM to hold another copy of the prevalent system. */ public void configureTransactionFiltering(boolean transactionFiltering) { _transactionFiltering = transactionFiltering; } /** * Configures the size (in bytes) of the journal file. When the current * journal exceeds this size, a new journal is created. */ public void configureJournalFileSizeThreshold(long sizeInBytes) { _journalSizeThreshold = sizeInBytes; } /** * Sets the age (in milliseconds) of the journal file. When the current * journal expires, a new journal is created. */ public void configureJournalFileAgeThreshold(long ageInMilliseconds) { _journalAgeThreshold = ageInMilliseconds; } public void configureJournalSerializer(JavaSerializer serializer) { configureJournalSerializer("journal", serializer); } public void configureJournalSerializer(XStreamSerializer serializer) { configureJournalSerializer("xstreamjournal", serializer); } public void configureJournalSerializer(SkaringaSerializer serializer) { configureJournalSerializer("skaringajournal", serializer); } /** * Configures the transaction journal Serializer to be used by the Prevayler * created by this factory. Only one Serializer is supported at a time. If * you want to change the Serializer of a system in production, you will * have to take a snapshot first because the journal files written by the * previous Serializer will not be read. */ public void configureJournalSerializer(String suffix, Serializer serializer) { PrevaylerDirectory.checkValidJournalSuffix(suffix); if (_journalSerializer != null) { throw new IllegalStateException("Read the javadoc to this method."); } _journalSerializer = serializer; _journalSuffix = suffix; } public void configureNetwork(OldNetwork network) { _network = network; } public void configureSnapshotSerializer(JavaSerializer serializer) { configureSnapshotSerializer("snapshot", serializer); } public void configureSnapshotSerializer(XStreamSerializer serializer) { configureSnapshotSerializer("xstreamsnapshot", serializer); } public void configureSnapshotSerializer(SkaringaSerializer serializer) { configureSnapshotSerializer("skaringasnapshot", serializer); } /** * Configure a serialization strategy for snapshots. This may be called any * number of times with different suffixes to configure different strategies * for reading existing snapshots. The first call to this method establishes * the <i>primary</i> strategy, which will be used for writing snapshots as * well as for deep-copying the prevalent system whenever necessary. */ public void configureSnapshotSerializer(String suffix, Serializer serializer) { PrevaylerDirectory.checkValidSnapshotSuffix(suffix); _snapshotSerializers.put(suffix, serializer); if (_primarySnapshotSuffix == null) { _primarySnapshotSuffix = suffix; } } /** * Returns a Prevayler created according to what was defined by calls to the * configuration methods above. * * @throws IOException * If there is trouble creating the Prevalence Base directory or * reading a .journal or .snapshot file. * @throws ClassNotFoundException * If a class of a serialized Object is not found when reading a * .journal or .snapshot file. */ public Prevayler create() throws IOException, ClassNotFoundException { GenericSnapshotManager snapshotManager = snapshotManager(); TransactionPublisher publisher; if (true) publisher = publisher(snapshotManager); else publisher = publisher(); if (_serverPort != -1) new ServerListener(publisher, network(), _serverPort); if (true) return new PrevaylerImpl(snapshotManager, publisher, journalSerializer()); else return new PrevaylerImpl( publisher, journalSerializer()); } private String prevalenceDirectory() { return _prevalenceDirectory != null ? _prevalenceDirectory : "Prevalence"; } private Object prevalentSystem() { if (_prevalentSystem == null) throw new IllegalStateException( "The prevalent system must be configured."); return _prevalentSystem; } private TransactionPublisher publisher( GenericSnapshotManager snapshotManager) throws IOException { if (_remoteServerIpAddress != null) return new ClientPublisher(network(), _remoteServerIpAddress, _remoteServerPort); if (true) return new CentralPublisher(clock(), censor(snapshotManager), journal()); else return new CentralPublisher(clock(), journal()); } private TransactionPublisher publisher() throws IOException { if (_remoteServerIpAddress != null) return new ClientPublisher(network(), _remoteServerIpAddress, _remoteServerPort); return new CentralPublisher(clock(), journal()); } private TransactionCensor censor(GenericSnapshotManager snapshotManager) { return _transactionFiltering ? (TransactionCensor) new StrictTransactionCensor( snapshotManager) : new LiberalTransactionCensor(); } private Journal journal() throws IOException { if (_transientMode) { return (Journal) new TransientJournal(); } else { PrevaylerDirectory directory = new PrevaylerDirectory( prevalenceDirectory()); if (true) return new PersistentJournal(directory, _journalSizeThreshold, _journalAgeThreshold, journalSuffix(), monitor()); else return new PersistentJournal(directory, _journalSizeThreshold, _journalAgeThreshold, journalSuffix()); } } private Serializer journalSerializer() { if (_journalSerializer != null) return _journalSerializer; return new JavaSerializer(); } private String journalSuffix() { return _journalSuffix != null ? _journalSuffix : "journal"; } private OldNetwork network() { return _network != null ? _network : new OldNetworkImpl(); } private GenericSnapshotManager snapshotManager() throws ClassNotFoundException, IOException { if (_nullSnapshotManager != null) return _nullSnapshotManager; PrevaylerDirectory directory = new PrevaylerDirectory( prevalenceDirectory()); if (!_snapshotSerializers.isEmpty()) return new GenericSnapshotManager(_snapshotSerializers, _primarySnapshotSuffix, prevalentSystem(), directory, journalSerializer()); String snapshotSuffix = "snapshot"; JavaSerializer snapshotSerializer = new JavaSerializer(); return new GenericSnapshotManager(Collections.singletonMap( snapshotSuffix, snapshotSerializer), snapshotSuffix, prevalentSystem(), directory, journalSerializer()); } private Monitor monitor() { return _monitor != null ? _monitor : new SimpleMonitor(System.err); } }