package org.marketcetera.module;
import org.marketcetera.util.misc.ClassVersion;
import org.marketcetera.util.misc.NamedThreadFactory;
import org.marketcetera.util.log.I18NBoundMessage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.Hashtable;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
/* $License$ */
/**
* A module that emits data.
*
* @author anshul@marketcetera.com
*/
@ClassVersion("$Id: EmitterModule.java 16154 2012-07-14 16:34:05Z colin $")
public class EmitterModule extends ModuleBase implements DataEmitter {
protected EmitterModule() {
super(EmitterModuleFactory.INSTANCE_URN);
}
@Override
protected void preStart() throws ModuleException {
super.preStart();
mService = Executors.newCachedThreadPool(
new NamedThreadFactory("TestEmitterModule"));
}
@Override
public void preStop() throws ModuleException {
super.preStop();
mService.shutdown();
}
@Override
public void requestData(DataRequest inRequest,
DataEmitterSupport inSupport)
throws UnsupportedRequestParameterType,
IllegalRequestParameterValue {
//Reset the flag to make testing easier
setThrowExceptionOnCancel(false);
Object obj = inRequest.getData();
if(obj == null) {
throw new IllegalRequestParameterValue(getURN(), obj);
}
if(!(obj instanceof String ||
obj instanceof Number ||
obj instanceof Map ||obj instanceof Object[])) {
throw new UnsupportedRequestParameterType(getURN(), obj);
}
mLastTask = new EmitTask(obj, inSupport);
Future<Integer> handle = mService.submit(mLastTask);
mRequests.put(inSupport.getRequestID(),handle);
mFlowIDs.add(inSupport.getFlowID());
}
@Override
public void cancel(DataFlowID inFlowID, RequestID inID) {
mFlowIDs.remove(inFlowID);
Future<Integer> handle = getTask(inID);
if(handle != null) {
// handles are not removed from the table so that
// we can do extra validation after.
handle.cancel(true);
}
if(mThrowExceptionOnCancel) {
throw new IllegalArgumentException();
}
}
/**
* Returns the set of requests that are currently being
* handled and have been handled in the past by this module.
*
* @return the set of all requests hendled by this module.
*/
public Set<RequestID> getRequests() {
return new HashSet<RequestID>(mRequests.keySet());
}
/**
* Returns the set of data flow IDs for the flows that are currently
* being handled by this module.
*
* @return the set of data flow IDs for the flows that are currently
* being handled by this module.
*/
public Set<DataFlowID> getFlows() {
return new HashSet<DataFlowID>(mFlowIDs);
}
/**
* Clears the set of requests handled by this module
*/
public void clear() {
mRequests.clear();
}
/**
* Returns the task handle corresponding to specified request.
*
* @param inRequestID the request ID.
*
* @return the task handle.
*/
public Future<Integer> getTask(RequestID inRequestID) {
return mRequests.get(inRequestID);
}
/**
* Invoked by the task to wait before it requests a stop via
* the data emit support interface or it emits a null value.
*
* @throws InterruptedException if the wait is interrupted.
*/
private static void waitToProceed() throws InterruptedException {
synchronized (STOP_LOCK) {
if (!sReadyToProceed) {
STOP_LOCK.wait();
}
//Reset the flag, for the next test.
sReadyToProceed = false;
}
}
/**
* Invoked by testing code to allow the task to proceed to request a stop
* via the data emit interface or emit a null value.
*/
static void readyToProceed() {
synchronized (STOP_LOCK) {
sReadyToProceed = true;
STOP_LOCK.notify();
}
}
/**
* Gets the last task that was executed by this module.
*
* @return the last task executed by this module.
*/
public EmitTask getLastTask() {
return mLastTask;
}
/**
* If {@link #cancel(DataFlowID, RequestID)} should throw an exception.
*
* @param inThrowExceptionOnCancel if the module should throw
* an exception when canceling a request.
*/
public void setThrowExceptionOnCancel(boolean inThrowExceptionOnCancel) {
mThrowExceptionOnCancel = inThrowExceptionOnCancel;
}
private static final Object STOP_LOCK = new Object();
private static boolean sReadyToProceed = false;
/**
* A task thats run in a separate thread to emit data.
*/
static class EmitTask implements Callable<Integer> {
public Integer call() throws Exception {
int i = 0;
try {
//Loop a maximum of 10 times
while(i < 10) {
if (mData instanceof Map) {
Map m = (Map) mData;
Object obj = m.get("value");
Object error = m.get("error");
int times = (Integer)m.get("times");
Boolean requestStop = (Boolean)m.get("requestStop");
//will cause the processor to request data flow stop.
boolean emitNull = m.containsKey("emitNull");
while(times > 0) {
mSupport.send(obj);
if(error != null && error instanceof I18NBoundMessage) {
mSupport.dataEmitError((I18NBoundMessage) error,false);
}
--times;
}
//Use a special marker to mark the end of this transmission.
mSupport.send(Boolean.FALSE);
if(emitNull || (requestStop != null && requestStop)) {
//wait if asked to emit null or request stop.
waitToProceed();
}
if(emitNull) {
mSupport.send(null);
}
//if asked to request data flow stop, do it
if(requestStop != null && requestStop) {
mSupport.dataEmitError(
TestMessages.STOP_DATA_FLOW, true);
}
} else if(mData instanceof Object[]){
for(Object o: (Object[])mData) {
mSupport.send(o);
}
} else {
mSupport.send(mData);
}
i++;
//Sleep for long to be able to test thread's interruption.
Thread.sleep(1000000); // 10 seconds
}
} catch (InterruptedException ignore) {
}
return i;
}
private EmitTask(Object inData, DataEmitterSupport inSupport) {
mData = inData;
mSupport = inSupport;
}
public DataEmitterSupport getSupport() {
return mSupport;
}
private Object mData;
private DataEmitterSupport mSupport;
}
private ExecutorService mService;
private EmitTask mLastTask;
private final Set<DataFlowID> mFlowIDs = new HashSet<DataFlowID>();
private final Hashtable<RequestID,Future<Integer>> mRequests =
new Hashtable<RequestID, Future<Integer>>();
private boolean mThrowExceptionOnCancel = false;
}