package org.marketcetera.modules.cep.system;
import org.marketcetera.core.Pair;
import org.marketcetera.module.*;
import org.marketcetera.util.log.I18NBoundMessage1P;
import org.marketcetera.util.misc.ClassVersion;
import java.util.HashMap;
import java.util.Map;
/**
* Simple straight-through implementation of the CEP module that
* filters received data and only emits data that match the type specified in the query.
* Only allows for <code>"select * from type_name"</code> type of queries.
* The <code>type_name</code> can be any alias listed in {@link CEPDataTypes} or any valid Java class name
*
* <p>
* Module Features
* <table>
* <tr><th>Capabilities</th><td>Data Emitter, Data Receiver</td></tr>
* <tr><th>DataFlow Request Parameters</th><td><code>String</code>: The CEP query</td></tr>
* <tr><th>Stops data flows</th><td>No</td></tr>
* <tr><th>Start Operation</th><td>Does nothing</td></tr>
* <tr><th>Stop Operation</th><td>Does nothing</td></tr>
* <tr><th>Management Interface</th><td>None</td></tr>
* <tr><th>Factory</th><td>{@link CEPSystemFactory}</td></tr>
* </table>
*
* The maps in the data structure are as follows:
* <ul>
* <li>{@link #mTypeLookupMap} is a mapping of all expected types to underlying classes (ie string --> class)
* for the purposes of doing the 'select * from <em>alias</em>' query</li>
* <li>{@link #mRequestMap} - map of {@link RequestID} --> pair of {class, {@link DataEmitterSupport}}. Given a requestID,
* we can get the class and corresponding emitter registered to listen on that type. For cancels,
* we pull out all the classes, and remove the emitter subscribed to listen on that event type</li>
* </ul>
*
* @author anshul@marketcetera.com
* @since 1.0.0
* @version $Id: CEPSystemProcessor.java 16841 2014-02-20 19:59:04Z colin $
*/
@ClassVersion("$Id: CEPSystemProcessor.java 16841 2014-02-20 19:59:04Z colin $") //$NON-NLS-1$
public class CEPSystemProcessor extends Module
implements DataReceiver, DataEmitter {
private static final String QUERY_DELIM = "[ \t]+"; //$NON-NLS-1$
private static final String QUERY_PREFIX = "select * from "; //$NON-NLS-1$
private static final String[] QUERY_SPLIT = QUERY_PREFIX.split(QUERY_DELIM);
private final HashMap<RequestID, Pair<Class<?>, DataEmitterSupport>> mRequestMap;
private final static Map<String, Class<?>> mTypeLookupMap = new HashMap<String, Class<?>>(20);
static {
for (Pair<String, Class<?>> pair : CEPDataTypes.REQUEST_PRECANNED_TYPES) {
mTypeLookupMap.put(pair.getFirstMember(), pair.getSecondMember());
}
}
protected CEPSystemProcessor(ModuleURN inURN, boolean inAutoStart) {
super(inURN, inAutoStart);
mRequestMap = new HashMap<RequestID, Pair<Class<?>, DataEmitterSupport>>();
}
@Override
protected void preStart() throws ModuleException {
// no-op
}
@Override
protected void preStop() throws ModuleException {
mRequestMap.clear();
}
/** Map the incoming data to some type, find the list of all {@link DataEmitterSupport} objects
* and send the data on its way there
* Ignore the flowID
* This is a very inefficient implementation - just iterate over all known requests
* and if we find a match send the data that way
*/
@Override
public void receiveData(DataFlowID inFlowID, Object inData) throws ReceiveDataException {
if(inData != null) {
for (Pair<Class<?>, DataEmitterSupport> classEmitterPair : mRequestMap.values()) {
if(classEmitterPair.getFirstMember().isAssignableFrom(inData.getClass())) {
classEmitterPair.getSecondMember().send(inData);
}
}
}
//ignore null data
}
@Override
public void requestData(DataRequest inRequest, DataEmitterSupport inSupport) throws RequestDataException {
if(inRequest == null) {
throw new IllegalRequestParameterValue(getURN(), null);
}
Object obj = inRequest.getData();
if(obj == null) {
throw new IllegalRequestParameterValue(getURN(), null);
}
String query;
if(obj instanceof String) {
query = (String)obj;
} else {
throw new UnsupportedRequestParameterType(getURN(), obj);
}
String[] querySplit = query.split(QUERY_DELIM);
if (querySplit.length != 4 || !QUERY_SPLIT[0].equals(querySplit[0])
||!QUERY_SPLIT[1].equals(querySplit[1]) ||!QUERY_SPLIT[2].equals(querySplit[2])) {
throw new RequestDataException(new I18NBoundMessage1P(Messages.INVALID_QUERY, query));
}
// find the type they are requesting (ie select * from <type>)
String type = query.substring(QUERY_PREFIX.length());
Class<?> theClass = getClassForRequest(type);
if (theClass == null) {
throw new RequestDataException(new I18NBoundMessage1P(Messages.UNSUPPORTED_TYPE, type));
}
Pair<Class<?>, DataEmitterSupport> request = new Pair<Class<?>, DataEmitterSupport>(theClass, inSupport);
mRequestMap.put(inSupport.getRequestID(), request);
}
/** Find the request, and go through all its types and remove all the {@link DataEmitterSupport}
* object associated with it */
@Override
public void cancel(DataFlowID inFlowID, RequestID inRequestID) {
mRequestMap.remove(inRequestID);
}
/** Checks to see if we are looking at an alias or a fully-qualified class name.
* Any known alias or valid FQCN is allowed
*/
protected Class<?> getClassForRequest(String className) {
// first check to see if it's a known pre-canned type
Class<?> theClass = mTypeLookupMap.get(className);
if(theClass != null) return theClass;
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
return null;
}
}
}