package org.marketcetera.module; import org.marketcetera.util.misc.ClassVersion; import org.marketcetera.util.log.I18NBoundMessage; import org.marketcetera.util.log.SLF4JLoggerProxy; import org.marketcetera.util.except.I18NException; import java.util.concurrent.atomic.AtomicLong; /* $License$ */ /** * Base class for couplers. A coupler receives data from an emitter * and supplies it to a receiver at the other end. * * A data flow consists of two or more modules connected with each * other through couplers. * * A coupler keeps the statistics on number of data objects * received from the emitter module and received by the receiver * module. It also keeps statistics on the number of errors * generated by the modules when emitting / receiving data and * the last emit / receive error text. * * Subclasses typically implement the {@link #process(Object)} method. * The <code>process()</code> method is called when the emitter module * emits data. From within <code>process()</code> method subclasses * eventually invoke {@link #receive(Object)} method to supply * the emitted data to the receiver module. * * @author anshul@marketcetera.com * @version $Id: AbstractDataCoupler.java 16154 2012-07-14 16:34:05Z colin $ * @since 1.0.0 */ @ClassVersion("$Id: AbstractDataCoupler.java 16154 2012-07-14 16:34:05Z colin $") //$NON-NLS-1$ abstract class AbstractDataCoupler implements DataEmitterSupport { @Override public final void send(Object inData) { //ignore data if the request has been canceled if(mRequestCanceled) { return; } mEmitted.incrementAndGet(); SLF4JLoggerProxy.debug(this,"Module {} emitted \"{}\"", //$NON-NLS-1$ mEmitter.getURN(), inData); process(inData); } @Override public final void dataEmitError(I18NBoundMessage inMessage, boolean inStopDataFlow) { if(mRequestCanceled) { return; } mEmitErrors.incrementAndGet(); mLastEmitError = inMessage.getText(); Messages.LOG_MODULE_EMIT_ERROR.warn(this, mEmitter.getURN(), inMessage.getText()); if(inStopDataFlow) { cancelDataFlow(mEmitter); } } /** * The request ID associated with this request. * * @return the request ID. */ @Override public final RequestID getRequestID() { return mRequestID; } /** * The flowID uniquely identifying this data flow. * * @return the flowID uniquely identifying this data flow. */ @Override public final DataFlowID getFlowID() { return mFlowID; } /** * The number of data instances received by this coupler. * * @return number of data instances received. */ public final long getReceived() { return mReceived.longValue(); } /** * The number of data instances emitted to this coupler. * * @return number of data instances emitted. */ public final long getEmitted() { return mEmitted.longValue(); } /** * The number of data receive errors encountered. * * @return number of data receive errors encountered. */ public final long getReceiveErrors() { return mReceiveErrors.longValue(); } /** * The number of data emit errors encountered. * * @return number of data receive errors encountered. */ public final long getEmitErrors() { return mEmitErrors.longValue(); } /** * The text from the last data receive error. * * @return text from the last data receive error. */ public final String getLastReceiveError() { return mLastReceiveError; } /** * The text from the last data emit error. * * @return text from the last data emit error. */ public final String getLastEmitError() { return mLastEmitError; } /** * This method is implemented by the subclasses to receive * the data emitted by the emitter module. The implementation * of this method should eventually call {@link #receive(Object)} * to deliver the data object to receiver on this coupling. * * @param inData the data. */ protected abstract void process(Object inData); /** * This method is invoked to supply the data emitted by * the emitter to the receiver of this data. This method * is typically invoked by the subclasses from within * the implementation of {@link #process(Object)} * * @param inData the data object, can be null */ protected final void receive(Object inData) { try { mReceived.incrementAndGet(); boolean failed = true; try { ((DataReceiver)mReceiver).receiveData(mFlowID,inData); failed = false; SLF4JLoggerProxy.debug(this, "{} received {}", //$NON-NLS-1$ mReceiver.getURN(), mReceived); } finally { if(failed) { //This counter needs to be incremented before //data flow is cancelled. mReceiveErrors.incrementAndGet(); } } } catch (Throwable t) { if(t instanceof I18NException) { mLastReceiveError = ((I18NException)t).getLocalizedDetail(); } else { mLastReceiveError = t.getLocalizedMessage(); } Messages.LOG_DATA_RECEIVE_ERROR.warn(this, t, mReceiver.getURN(), inData); if(t instanceof StopDataFlowException) { Messages.LOG_CANCELING_DATA_FLOW.info(this, t, mFlowID, getReceiverURN()); cancelDataFlow(mReceiver); } } } /** * Creates new instance. * * @param inManager the module manager instance * @param inEmitter the emitter module instance * @param inReceiver the receiver module instance * @param inFlowID the data flow ID */ protected AbstractDataCoupler(ModuleManager inManager, Module inEmitter, Module inReceiver, DataFlowID inFlowID) { mManager = inManager; mEmitter = inEmitter; mReceiver = inReceiver; mFlowID = inFlowID; } /** * Initiates a request with the data emitter using the request parameter * in specified request. * * @param inRequestID the requestID uniquely identifying request. * @param inRequest the request. * * @throws RequestDataException if the module is unable to fulfill * this request. */ final void initiateRequest(RequestID inRequestID, DataRequest inRequest) throws RequestDataException { mRequestID = inRequestID; sNestedFlowCall.set(Boolean.TRUE); try { ((DataEmitter)mEmitter).requestData(inRequest, this); } finally { sNestedFlowCall.set(Boolean.FALSE); } } /** * Cancels the request with the emitter module and * disables all the communication through the coupling */ final void cancelRequest() { try { sNestedFlowCall.set(Boolean.TRUE); try { ((DataEmitter)mEmitter).cancel(mFlowID, mRequestID); } finally { sNestedFlowCall.set(Boolean.FALSE); } } catch(Throwable t) { Messages.LOG_UNEXPECTED_ERROR_CANCELING_REQ.warn( this,t, mRequestID); } finally { mRequestCanceled = true; } } /** * The emitter module's URN. * * @return emitter module's URN. */ final ModuleURN getEmitterURN() { return mEmitter.getURN(); } /** * The receiver module's URN. * * @return receiver module's URN. */ final ModuleURN getReceiverURN() { return mReceiver.getURN(); } /** * Returns true, if this method is being invoked from within a * {@link DataEmitter#requestData(DataRequest, DataEmitterSupport)} or * a {@link DataEmitter#cancel(DataFlowID, RequestID)}. * * @return true if this method has <code>DataEmitter.requestData()</code> * or <code>DataEmitter.cancel()</code> on its calling stack. */ static boolean isNestedFlowCall() { return sNestedFlowCall.get(); } /** * Cancels the data flow as requested by either the emitter * or receiver module. * * @param inRequester the module requesting cancellation of * the data flow. */ private void cancelDataFlow(Module inRequester) { try { mManager.cancel(mFlowID,inRequester); } catch (Exception e) { Messages.LOG_UNEXPECTED_ERROR_CANCELING_FLOW.error(this, e, mFlowID, inRequester.getURN()); } } private final ModuleManager mManager; private final Module mEmitter; private final Module mReceiver; private final AtomicLong mReceived = new AtomicLong(0); private final AtomicLong mEmitted = new AtomicLong(0); private final AtomicLong mReceiveErrors = new AtomicLong(0); private final AtomicLong mEmitErrors = new AtomicLong(0); private final DataFlowID mFlowID; /* * The following variables are kept as volatile to avoid overhead * due to synchronization. Its assumed that these variables * are updated from a single thread but may be read from multiple * threads, in which case, we shouldn't have any data consistency * issues. */ private volatile String mLastReceiveError = null; private volatile String mLastEmitError = null; private volatile boolean mRequestCanceled = false; private volatile RequestID mRequestID; private final static ThreadLocal<Boolean> sNestedFlowCall = new ThreadLocal<Boolean>(){ @Override protected Boolean initialValue() { return Boolean.FALSE; } }; }