Skip to content

Commit

Permalink
Merge pull request #1 from Navigateur/master
Browse files Browse the repository at this point in the history
Type-safe Prevayler
  • Loading branch information
karlwettin committed Nov 5, 2011
2 parents 7471c6d + 7e1242c commit 13ff94e
Show file tree
Hide file tree
Showing 68 changed files with 342 additions and 301 deletions.
21 changes: 21 additions & 0 deletions CHANGES.txt
@@ -1,3 +1,24 @@
===========================================
Changes from Prevayler 2.5 to Prevayler 2.6

---------------------------
Type-safety and convenience

Prevayler, PrevaylerFactory, transactions and queries are now generic types
which can be parameterized so that prevalent and return objects and values
arrive in original type form. Compiler provides you warning where you don't
do this in your code.

No compilation without error unless prevalent type implements Serializable.

Existing code written with Prevayler 2.5 and earlier will still compile and
work EXCEPT for the minimum need for the first argument type of each custom
transaction and query's "executeOn"/"executeAndQuery"/"query" method to be
"Serializable" instead of "Object". This is if you still don't intend to
implement the new type-safety.

Updated demo code shows the type-safe usage.

===========================================
Changes from Prevayler 2.4 to Prevayler 2.5

Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTORS.txt
@@ -1,5 +1,6 @@
Thanks to Prevayler contributors:


Naveen Chawla
Aleksey Aristov
Eric Bridgwater
Toby Cabot
Expand Down
17 changes: 9 additions & 8 deletions core/src/main/java/org/prevayler/Prevayler.java
Expand Up @@ -6,17 +6,18 @@

import java.io.File;
import java.io.IOException;
import java.io.Serializable;


/** Implementations of this interface can provide transparent persistence and replication to all Business Objects in a Prevalent System. ALL operations that alter the observable state of the Prevalent System must be implemented as Transaction or TransactionWithQuery objects and must be executed using the Prevayler.execute(...) methods.
* See the demo applications in org.prevayler.demos for examples.
* @see PrevaylerFactory
*/
public interface Prevayler {
public interface Prevayler<P extends Serializable>{

/** Returns the Object which holds direct or indirect references to all other Business Objects in the system.
/** Returns the object which holds direct or indirect references to all other Business Objects in the system.
*/
public Object prevalentSystem();
public P prevalentSystem();

/** Returns the Clock used to determine the execution time of all Transaction and Queries executed using this Prevayler. This Clock is useful only to Communication Objects and must NOT be used by Transactions, Queries or Business Objects, since that would make them become non-deterministic. Instead, Transactions, Queries and Business Objects must use the executionTime parameter which is passed on their execution.
*/
Expand All @@ -26,27 +27,27 @@ public interface Prevayler {
* Implementations of this interface can log the given Transaction for crash or shutdown recovery, for example, or execute it remotely on replicas of the prevalentSystem() for fault-tolerance and load-balancing purposes.
* @see PrevaylerFactory
*/
public void execute(Transaction transaction);
public void execute(Transaction<P> transaction);

/** Executes the given sensitiveQuery on the prevalentSystem(). A sensitiveQuery is a Query that would be affected by the concurrent execution of a Transaction or other sensitiveQuery. This method synchronizes on the prevalentSystem() to execute the sensitiveQuery. It is therefore guaranteed that no other Transaction or sensitiveQuery is executed at the same time.
* <br> Robust Queries (queries that do not affect other operations and that are not affected by them) can be executed directly as plain old method calls on the prevalentSystem() without the need of being implemented as Query objects. Examples of Robust Queries are queries that read the value of a single field or historical queries such as: "What was this account's balance at mid-night?".
* @return The result returned by the execution of the sensitiveQuery on the prevalentSystem().
* @throws Exception The Exception thrown by the execution of the sensitiveQuery on the prevalentSystem().
*/
public Object execute(Query sensitiveQuery) throws Exception;
public <R> R execute(Query<P,R> sensitiveQuery) throws Exception;

/** Executes the given transactionWithQuery on the prevalentSystem().
* Implementations of this interface can log the given transaction for crash or shutdown recovery, for example, or execute it remotely on replicas of the prevalentSystem() for fault-tolerance and load-balancing purposes.
* @return The result returned by the execution of the transactionWithQuery on the prevalentSystem().
* @throws Exception The Exception thrown by the execution of the sensitiveQuery on the prevalentSystem().
* @see PrevaylerFactory
*/
public Object execute(TransactionWithQuery transactionWithQuery) throws Exception;
public <R> R execute(TransactionWithQuery<P,R> transactionWithQuery) throws Exception;

/** The same as execute(TransactionWithQuery) except no Exception is thrown.
/** The same as execute(TransactionWithQuery<P,R>) except no Exception is thrown.
* @return The result returned by the execution of the sureTransactionWithQuery on the prevalentSystem().
*/
public Object execute(SureTransactionWithQuery sureTransactionWithQuery);
public <R> R execute(SureTransactionWithQuery<P,R> sureTransactionWithQuery);

/** Produces a complete serialized image of the underlying PrevalentSystem.
* This will accelerate future system startups. Taking a snapshot once a day is enough for most applications.
Expand Down
7 changes: 4 additions & 3 deletions core/src/main/java/org/prevayler/Query.java
Expand Up @@ -4,19 +4,20 @@

package org.prevayler;

import java.io.Serializable;
import java.util.Date;

/** Represents a query that can be executed on a Prevalent System.
* @see org.prevayler.Prevayler#execute(Query)
* @see org.prevayler.Prevayler#execute(Query<P,R>)
*/
public interface Query {
public interface Query<P extends Serializable,R> extends Serializable{

/**
* @param prevalentSystem The Prevalent System to be queried.
* @param executionTime The "current" time.
* @return The result of this Query.
* @throws Exception Any Exception encountered by this Query.
*/
public Object query(Object prevalentSystem, Date executionTime) throws Exception;
public R query(P prevalentSystem, Date executionTime) throws Exception;

}
Expand Up @@ -4,16 +4,17 @@

package org.prevayler;

import java.io.Serializable;
import java.util.Date;

/** The same as TransactionWithQuery except it does not throw Exception when executed.
* @see TransactionWithQuery
*/
public interface SureTransactionWithQuery extends TransactionWithQuery {
public interface SureTransactionWithQuery<P extends Serializable,R> extends TransactionWithQuery<P,R>{

/** The same as TransactionWithQuery.execute(Object, Date) except it does not throw Exception when executed.
* @see TransactionWithQuery#executeAndQuery(Object, Date)
/** The same as TransactionWithQuery.executeAndQuery(P, Date) except it does not throw Exception when executed.
* @see TransactionWithQuery#executeAndQuery(P, Date)
*/
public Object executeAndQuery(Object prevalentSystem, Date executionTime);
public R executeAndQuery(P prevalentSystem, Date executionTime);

}
4 changes: 2 additions & 2 deletions core/src/main/java/org/prevayler/Transaction.java
Expand Up @@ -9,12 +9,12 @@

/** An atomic Transaction to be executed on a Prevalent System. Any operation which changes the observable state of a prevalent system must be encapsulated as a Transaction. <br><br> IMPORTANT: Transactions CANNOT reference business objects directly. Instead, they must search the business objects they need given the Prevalent System. See org.prevayler.demos for usage examples. <br><br> Business objects referenced in a transaction will be mere copies of the original business objects when that transaction is recovered from the serialized journal file. This will make the transactions work when they are executed for the first time but have no effect during shutdown recovery. This is known as the prevalence baptism problem because everyone comes across it, despite of this warning.
*/
public interface Transaction extends Serializable {
public interface Transaction<P extends Serializable> extends Serializable{

/** This method is called by Prevayler.execute(Transaction) to execute this Transaction on the given Prevalent System. See org.prevayler.demos for usage examples.
* @param prevalentSystem The system on which this Transaction will execute.
* @param executionTime The time at which this Transaction is being executed. Every Transaction executes completely within a single moment in time. Logically, a Prevalent System's time does not pass during the execution of a Transaction.
*/
public void executeOn(Object prevalentSystem, Date executionTime);
public void executeOn(P prevalentSystem, Date executionTime);

}
8 changes: 4 additions & 4 deletions core/src/main/java/org/prevayler/TransactionWithQuery.java
Expand Up @@ -10,13 +10,13 @@
/** A Transaction that also returns a result or throws an Exception after executing. <br><br>A "PersonCreation" Transaction, for example, may return the Person it created. Without this, to retrieve the newly created Person, the caller would have to issue a Query like: "What was the last Person I created?". <br><br>Looking at the Prevayler demos is by far the best way to learn how to use this class.
* @see Transaction
*/
public interface TransactionWithQuery extends Serializable {
public interface TransactionWithQuery<P extends Serializable,R> extends Serializable {

/** Performs the necessary modifications on the given prevalentSystem and also returns an Object or throws an Exception.
* This method is called by Prevayler.execute(TransactionWithQuery) to execute this TransactionWithQuery on the given Prevalent System. See org.prevayler.demos for usage examples.
/** Performs the necessary modifications on the given prevalentSystem and also returns an object or throws an Exception.
* This method is called by Prevayler.execute(TransactionWithQuery<P,R>) to execute this TransactionWithQuery on the given Prevalent System. See org.prevayler.demos for usage examples.
* @param prevalentSystem The system on which this TransactionWithQuery will execute.
* @param executionTime The time at which this TransactionWithQuery is being executed. Every Transaction executes completely within a single moment in time. Logically, a Prevalent System's time does not pass during the execution of a Transaction.
*/
public Object executeAndQuery(Object prevalentSystem, Date executionTime) throws Exception;
public R executeAndQuery(P prevalentSystem, Date executionTime) throws Exception;

}
Expand Up @@ -11,7 +11,7 @@
/**
* Writes and reads objects using Java serialization. This serializer can be used for snapshots, journals or both.
*/
public class JavaSerializer implements Serializer {
public class JavaSerializer implements Serializer{

private ClassLoader _loader;

Expand Down
Expand Up @@ -2,6 +2,7 @@

import java.io.OutputStream;
import java.io.InputStream;
import java.io.Serializable;

/**
* A strategy for writing objects to and reading objects from streams. Implementations <b>must</b> be safe for
Expand All @@ -12,7 +13,7 @@
* for journals, it must be able to write and read any transactions it will be used with, but does not need to be
* able to write or read any other objects.
*/
public interface Serializer {
public interface Serializer{

/**
* Write an object to a stream. An implementation must ensure that the object is written
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/java/org/prevayler/implementation/Capsule.java
@@ -1,5 +1,6 @@
package org.prevayler.implementation;

import org.prevayler.Transaction;
import org.prevayler.foundation.Chunk;
import org.prevayler.foundation.serialization.Serializer;

Expand Down Expand Up @@ -61,7 +62,7 @@ public void executeOn(Object prevalentSystem, Date executionTime, Serializer jou
* is responsible for synchronizing on the prevalentSystem.
*/
protected abstract void justExecute(Object transaction, Object prevalentSystem, Date executionTime);

/**
* Make a clean copy of this capsule that will have its own query result fields.
*/
Expand Down
Expand Up @@ -11,23 +11,24 @@

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.Date;

public class PrevalentSystemGuard implements TransactionSubscriber {
public class PrevalentSystemGuard<P extends Serializable> implements TransactionSubscriber {

private Object _prevalentSystem; // All access to field is synchronized on "this", and all access to object is synchronized on itself; "this" is always locked before the object
private P _prevalentSystem; // All access to field is synchronized on "this", and all access to object is synchronized on itself; "this" is always locked before the object
private long _systemVersion; // All access is synchronized on "this"
private boolean _ignoreRuntimeExceptions; // All access is synchronized on "this"
private final Serializer _journalSerializer;

public PrevalentSystemGuard(Object prevalentSystem, long systemVersion, Serializer journalSerializer) {
public PrevalentSystemGuard(P prevalentSystem, long systemVersion, Serializer journalSerializer) {
_prevalentSystem = prevalentSystem;
_systemVersion = systemVersion;
_ignoreRuntimeExceptions = false;
_journalSerializer = journalSerializer;
}

public Object prevalentSystem() {
public P prevalentSystem() {
synchronized (this) {
if (_prevalentSystem == null) {
throw new Error("Prevayler is no longer allowing access to the prevalent system due to an Error thrown from an earlier transaction.");
Expand Down Expand Up @@ -82,7 +83,7 @@ public void receive(TransactionTimestamp transactionTimestamp) {
}
}

public Object executeQuery(Query sensitiveQuery, Clock clock) throws Exception {
public <R> R executeQuery(Query<P,R> sensitiveQuery, Clock clock) throws Exception {
synchronized (this) {
if (_prevalentSystem == null) {
throw new Error("Prevayler is no longer processing queries due to an Error thrown from an earlier transaction.");
Expand All @@ -94,7 +95,7 @@ public Object executeQuery(Query sensitiveQuery, Clock clock) throws Exception {
}
}

public File takeSnapshot(GenericSnapshotManager snapshotManager) throws Exception {
public File takeSnapshot(GenericSnapshotManager<P> snapshotManager) throws Exception {
synchronized (this) {
if (_prevalentSystem == null) {
throw new Error("Prevayler is no longer allowing snapshots due to an Error thrown from an earlier transaction.");
Expand All @@ -106,7 +107,7 @@ public File takeSnapshot(GenericSnapshotManager snapshotManager) throws Exceptio
}
}

public PrevalentSystemGuard deepCopy(long systemVersion, Serializer snapshotSerializer) throws Exception {
public PrevalentSystemGuard<P> deepCopy(long systemVersion, Serializer snapshotSerializer) throws Exception {
synchronized (this) {
while (_systemVersion < systemVersion && _prevalentSystem != null) {
Cool.wait(this);
Expand All @@ -121,7 +122,7 @@ public PrevalentSystemGuard deepCopy(long systemVersion, Serializer snapshotSeri
}

synchronized (_prevalentSystem) {
return new PrevalentSystemGuard(DeepCopier.deepCopyParallel(_prevalentSystem, snapshotSerializer), _systemVersion, _journalSerializer);
return new PrevalentSystemGuard<P>((P)DeepCopier.deepCopyParallel(_prevalentSystem, snapshotSerializer), _systemVersion, _journalSerializer);
}
}
}
Expand Down
25 changes: 13 additions & 12 deletions core/src/main/java/org/prevayler/implementation/PrevaylerImpl.java
Expand Up @@ -16,13 +16,14 @@

import java.io.File;
import java.io.IOException;
import java.io.Serializable;

public class PrevaylerImpl implements Prevayler {
public class PrevaylerImpl<P extends Serializable> implements Prevayler<P>{

private final PrevalentSystemGuard _guard;
private final PrevalentSystemGuard<P> _guard;
private final Clock _clock;

private final GenericSnapshotManager _snapshotManager;
private final GenericSnapshotManager<P> _snapshotManager;

private final TransactionPublisher _publisher;

Expand All @@ -36,7 +37,7 @@ public class PrevaylerImpl implements Prevayler {
* @param prevaylerMonitor The Monitor that will be used to monitor interesting calls to this PrevaylerImpl.
* @param journalSerializer
*/
public PrevaylerImpl(GenericSnapshotManager snapshotManager, TransactionPublisher transactionPublisher,
public PrevaylerImpl(GenericSnapshotManager<P> snapshotManager, TransactionPublisher transactionPublisher,
Serializer journalSerializer) throws IOException, ClassNotFoundException {
_snapshotManager = snapshotManager;

Expand All @@ -50,14 +51,14 @@ public PrevaylerImpl(GenericSnapshotManager snapshotManager, TransactionPublishe
_journalSerializer = journalSerializer;
}

public Object prevalentSystem() { return _guard.prevalentSystem(); }
public P prevalentSystem() { return _guard.prevalentSystem(); }


public Clock clock() { return _clock; }


public void execute(Transaction transaction) {
publish(new TransactionCapsule(transaction, _journalSerializer)); //TODO Optimizations: 1) The Censor can use the actual given transaction if it is Immutable instead of deserializing a new one from the byte array. 2) Make the baptism fail-fast feature optional (default is on). If it is off, the given transaction can be used instead of deserializing a new one from the byte array.
public void execute(Transaction<P> transaction) {
publish(new TransactionCapsule<P>(transaction, _journalSerializer)); //TODO Optimizations: 1) The Censor can use the actual given transaction if it is Immutable instead of deserializing a new one from the byte array. 2) Make the baptism fail-fast feature optional (default is on). If it is off, the given transaction can be used instead of deserializing a new one from the byte array.
}


Expand All @@ -66,21 +67,21 @@ private void publish(Capsule capsule) {
}


public Object execute(Query sensitiveQuery) throws Exception {
public <R> R execute(Query<P,R> sensitiveQuery) throws Exception {
return _guard.executeQuery(sensitiveQuery, clock());
}


public Object execute(TransactionWithQuery transactionWithQuery) throws Exception {
TransactionWithQueryCapsule capsule = new TransactionWithQueryCapsule(transactionWithQuery, _journalSerializer);
public <R> R execute(TransactionWithQuery<P,R> transactionWithQuery) throws Exception {
TransactionWithQueryCapsule<P,R> capsule = new TransactionWithQueryCapsule<P,R>(transactionWithQuery, _journalSerializer);
publish(capsule);
return capsule.result();
}


public Object execute(SureTransactionWithQuery sureTransactionWithQuery) {
public <R> R execute(SureTransactionWithQuery<P,R> sureTransactionWithQuery) {
try {
return execute((TransactionWithQuery)sureTransactionWithQuery);
return execute((TransactionWithQuery<P,R>)sureTransactionWithQuery);
} catch (RuntimeException runtime) {
throw runtime;
} catch (Exception checked) {
Expand Down
Expand Up @@ -3,13 +3,14 @@
import org.prevayler.Transaction;
import org.prevayler.foundation.serialization.Serializer;

import java.io.Serializable;
import java.util.Date;

class TransactionCapsule extends Capsule {
class TransactionCapsule<P extends Serializable> extends Capsule{

private static final long serialVersionUID = 3283271592697928351L;

public TransactionCapsule(Transaction transaction, Serializer journalSerializer) {
public TransactionCapsule(Transaction<P> transaction, Serializer journalSerializer) {
super(transaction, journalSerializer);
}

Expand All @@ -18,12 +19,12 @@ public TransactionCapsule(byte[] serialized) {
}

protected void justExecute(Object transaction, Object prevalentSystem, Date executionTime) {
((Transaction) transaction).executeOn(prevalentSystem, executionTime);
((Transaction<P>) transaction).executeOn((P)prevalentSystem, executionTime);
}

public Capsule cleanCopy() {
// TransactionCapsule, unlike TransactionWithQueryCapsule, is completely immutable.
return this;
}

}

0 comments on commit 13ff94e

Please sign in to comment.