package org.springmodules.javaspaces; import java.rmi.RemoteException; import net.jini.core.entry.Entry; import net.jini.core.entry.UnusableEntryException; 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.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springmodules.javaspaces.entry.AbstractMethodCallEntry; import org.springmodules.javaspaces.entry.MethodResultEntry; /** * Generic worker designed to run in a thread. Takes method call entries from a * JavaSpace. Can either download code or work with a local delegate. * <p> * Must be configured with a JavaSpaceTemplate helper object to use to read and * write from the JavaSpace, and a businessInterface (usually a single interface * that this worker will "implement"). * * @author Rod Johnson */ public class DelegatingWorker implements Runnable { private static final Log log = LogFactory.getLog(DelegatingWorker.class); private long waitMillis = 500; private Object delegate; private JavaSpaceTemplate jsTemplate; private boolean running = true; /** * Candidate that will match only this interface */ private Entry methodCallEntryTemplate; private Class businessInterface; public DelegatingWorker() { } public void setBusinessInterface(Class intf) { if (!intf.isInterface()) { throw new IllegalArgumentException(intf + " must be an interface"); } this.businessInterface = intf; } /** * Set a delegate if we are using the "service seeking" approach. For * RunnableMethodCallEntries, there is no need for a service to be hosted. * * @param delegate */ public void setDelegate(Object delegate) { this.delegate = delegate; } public void setJavaSpaceTemplate(JavaSpaceTemplate jsTemplate) { this.jsTemplate = jsTemplate; } public void stop() { this.running = false; } public void run() { AbstractMethodCallEntry t = new AbstractMethodCallEntry(); // Needed for match t.uid = null; t.className = businessInterface.getName(); this.methodCallEntryTemplate = jsTemplate.snapshot(t); final boolean debug = log.isDebugEnabled(); if (debug) log.debug("Worker " + this + " starting..."); while (running) { if (debug) log.debug("Worker " + this + " waiting..."); // On reading from the space, the result will be computed and // written // back into the space in one transaction jsTemplate.execute(new JavaSpaceCallback() { public Object doInSpace(JavaSpace js, Transaction transaction) throws RemoteException, TransactionException, UnusableEntryException, InterruptedException { // look for method call AbstractMethodCallEntry call = (AbstractMethodCallEntry) js.take(methodCallEntryTemplate, transaction, waitMillis); if (call == null) { // TODO is this required? if (debug) log.debug("Skipping out of loop..."); return null; } try { MethodResultEntry result = invokeMethod(call, delegate); // push the result back to the JavaSpace js.write(result, transaction, Lease.FOREVER); } catch (Exception ex) { // TODO fix me, should translate to JavaSpaceException // hierarchy throw new IllegalStateException(ex.getMessage()); } return null; } }); } if (debug) log.debug("Worker " + this + " terminating"); } /** * Invoke the method on the delegate object in order to get the * MethodResultEntry. Subclasses can extend the method and add custom * behavior (ex: security propagation). * * @param localDelegate the delegate used for executing the method * @return the methodResultEntry */ protected MethodResultEntry invokeMethod(AbstractMethodCallEntry call, Object localDelegate) throws IllegalAccessException { if (log.isDebugEnabled()) log.debug("call is " + call.getClass().getName()); MethodResultEntry result = call.invokeMethod(localDelegate); if (log.isDebugEnabled()) log.debug("Got result " + result.result); return result; } }