package org.springmodules.javaspaces; import java.rmi.MarshalledObject; import java.rmi.RemoteException; import net.jini.core.entry.Entry; import net.jini.core.entry.UnusableEntryException; import net.jini.core.event.EventRegistration; import net.jini.core.event.RemoteEventListener; import net.jini.core.lease.Lease; import net.jini.core.transaction.Transaction; import net.jini.core.transaction.TransactionException; import net.jini.space.JavaSpace; import org.springframework.beans.factory.InitializingBean; import org.springmodules.jini.JiniUtils; // TODO properly fix exception handling /** * Implementation of the Spring "template" concept for JavaSpaces. Translates * exceptions into Spring exception hierarchy. Simplifies the performance of * several operations in a single method. * * @author Rod Johnson * @author Costin Leau * @see org.springmodules.javaspaces.JavaSpaceCallback */ public class JavaSpaceTemplate implements InitializingBean { private JavaSpace space; /** * Required if operations with the space should be ran without a transaction * while there is a transaction already taken place. * */ private boolean useTransaction = true; public JavaSpaceTemplate() { } /** * constructor * @param space the space */ public JavaSpaceTemplate(JavaSpace space) { this.space = space; afterPropertiesSet(); } /** * @see org.springmodules.beans.factory.InitializingBean#afterPropertiesSet() * @throws IllegalArgumentException in case the JavaSpaces instance is null */ public void afterPropertiesSet() { if (space == null) throw new IllegalArgumentException("space property is required"); } /** * Write using the current transaction. * * @param entry * @param millis */ public Lease write(final Entry entry, final long millis) { return (Lease) execute(new JavaSpaceCallback() { public Object doInSpace(JavaSpace js, Transaction tx) throws RemoteException, TransactionException, UnusableEntryException { return js.write(entry, tx, millis); } }); } /** * The process of serializing an entry for transmission to a JavaSpaces * service will be identical if the same entry is used twice. This is most * likely to be an issue with templates that are used repeatedly to search * for entries with read or take. The client-side implementations of read * and take cannot reasonably avoid this duplicated effort, since they have * no efficient way of checking whether the same template is being used * without intervening modification. The snapshot method gives the * JavaSpaces service implementor a way to reduce the impact of repeated use * of the same entry. Invoking snapshot with an Entry will return another * Entry object that contains a snapshot of the original entry. Using the * returned snapshot entry is equivalent to using the unmodified original * entry in all operations on the same JavaSpaces service. Modifications to * the original entry will not affect the snapshot. You can snapshot a null * template; snapshot may or may not return null given a null template. The * entry returned from snapshot will be guaranteed equivalent to the * original unmodified object only when used with the space. Using the * snapshot with any other JavaSpaces service will generate an * IllegalArgumentException unless the other space can use it because of * knowledge about the JavaSpaces service that generated the snapshot. The * snapshot will be a different object from the original, may or may not * have the same hash code, and equals may or may not return true when * invoked with the original object, even if the original object is * unmodified. A snapshot is guaranteed to work only within the virtual * machine in which it was generated. If a snapshot is passed to another * virtual machine (for example, in a parameter of an RMI call), using * it--even with the same JavaSpaces service--may generate an * IllegalArgumentException. * * @param entry the entry to take a snapshot of. * @return a snapshot of the entry. */ public Entry snapshot(final Entry entry) { return (Entry) execute(new JavaSpaceCallback() { public Object doInSpace(JavaSpace js, Transaction tx) throws RemoteException { return js.snapshot(entry); } }); } /** * Take a matching entry from the space, waiting until one exists. Matching * is and timeout done as for read. * * @param template The template used for matching. Matching is done against * tmpl with null fields being wildcards ("match anything") other * fields being values ("match exactly on the serialized form"). * @param millis How long the client is willing to wait for a * transactionally proper matching entry. A timeout of NO_WAIT * means to wait no time at all; this is equivalent to a wait of * zero. * @return the entry taken from the space */ public Entry take(final Entry template, final long millis) { return (Entry) execute(new JavaSpaceCallback() { public Object doInSpace(JavaSpace js, Transaction tx) throws RemoteException, TransactionException, UnusableEntryException, InterruptedException { return js.take(template, tx, millis); } }); } /** * Take a matching entry from the space, returning null if there is * currently is none. Matching is and timeout done as for read, except that * blocking in this call is done only if necessary to wait for transactional * state to settle. * * @param template The template used for matching. Matching is done against * tmpl with null fields being wildcards ("match anything") other * fields being values ("match exactly on the serialized form"). * @param millis How long the client is willing to wait for a * transactionally proper matching entry. A timeout of NO_WAIT * means to wait no time at all; this is equivalent to a wait of * zero. * @return the entry taken from the space */ public Entry takeIfExists(final Entry template, final long millis) { return (Entry) execute(new JavaSpaceCallback() { public Object doInSpace(JavaSpace js, Transaction tx) throws RemoteException, TransactionException, UnusableEntryException, InterruptedException { return js.takeIfExists(template, tx, millis); } }); } /** * Read any matching entry from the space, returning null if there is * currently is none. Matching and timeouts are done as in read, except that * blocking in this call is done only if necessary to wait for transactional * state to settle. * * @param template The template used for matching. Matching is done against * tmpl with null fields being wildcards ("match anything") other * fields being values ("match exactly on the serialized form"). * @param millis How long the client is willing to wait for a * transactionally proper matching entry. A timeout of NO_WAIT * means to wait no time at all; this is equivalent to a wait of * zero. * @return a copy of the entry read from the space */ public Entry readIfExists(final Entry template, final long millis) { return (Entry) execute(new JavaSpaceCallback() { public Object doInSpace(JavaSpace js, Transaction tx) throws RemoteException, TransactionException, UnusableEntryException, InterruptedException { return js.readIfExists(template, tx, millis); } }); } /** * Read any matching entry from the space, blocking until one exists. Return * null if the timeout expires. * * @param template The template used for matching. Matching is done against * tmpl with null fields being wildcards ("match anything") other * fields being values ("match exactly on the serialized form"). * @param millis How long the client is willing to wait for a * transactionally proper matching entry. A timeout of NO_WAIT * means to wait no time at all; this is equivalent to a wait of * zero. * @return a copy of the entry read from the space */ public Entry read(final Entry template, final long millis) { return (Entry) execute(new JavaSpaceCallback() { public Object doInSpace(JavaSpace js, Transaction tx) throws RemoteException, TransactionException, UnusableEntryException, InterruptedException { return js.read(template, tx, millis); } }); } /** * When entries are written that match this template notify the given * listener with a RemoteEvent that includes the handback object. Matching * is done as for read. * * @param template tmpl - The template used for matching. Matching is done * against tmpl with null fields being wildcards ("match * anything") other fields being values ("match exactly on the * serialized form"). * @param listener The remote event listener to notify. * @param millis the requested lease time, in milliseconds * @param handback * @return An object to send to the listener as part of the event * notification. */ public EventRegistration notify(final Entry template, final RemoteEventListener listener, final long millis, final MarshalledObject handback) { return (EventRegistration) execute(new JavaSpaceCallback() { public Object doInSpace(JavaSpace js, Transaction tx) throws RemoteException, TransactionException, UnusableEntryException, InterruptedException { return js.notify(template, tx, listener, millis, handback); } }); } // TODO transaction nesting protected Transaction getCurrentTransaction() { return JiniUtils.getTransaction(getSpace()); } /** * Perform multiple JavaSpaces tasks in a single transaction. * @param jsc The javaspace callback * @return the result of the execution. */ public Object execute(JavaSpaceCallback jsc) { try { Transaction tx = (useTransaction ? getCurrentTransaction() : null); Object retval = jsc.doInSpace(this.space, tx); return retval; } catch (Exception ex) { throw convertSpaceException(ex); } finally { } } protected RuntimeException convertSpaceException(Exception e) { return JiniUtils.convertJiniException(e); } /** * @param space */ public void setSpace(JavaSpace space) { this.space = space; } /** * * @return the Javaspace this template operates on */ public JavaSpace getSpace() { return this.space; } /** * @return Returns the useTransaction. */ public boolean isUseTransaction() { return useTransaction; } /** * @param useTransaction * The useTransaction to set. */ public void setUseTransaction(boolean useTransaction) { this.useTransaction = useTransaction; } }