package org.marketcetera.strategy; import static org.marketcetera.strategy.Status.COMPILING; import static org.marketcetera.strategy.Status.FAILED; import static org.marketcetera.strategy.Status.STOPPED; import static org.marketcetera.strategy.Status.STOPPING; import static org.marketcetera.strategy.Status.UNSTARTED; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.Properties; import java.util.Set; import org.apache.commons.io.FileUtils; import org.marketcetera.core.ClassVersion; import org.marketcetera.event.AskEvent; import org.marketcetera.event.BidEvent; import org.marketcetera.event.DividendEvent; import org.marketcetera.event.MarketstatEvent; import org.marketcetera.event.TradeEvent; import org.marketcetera.event.impl.LogEventBuilder; import org.marketcetera.trade.ExecutionReport; import org.marketcetera.trade.OrderCancelReject; /* $License$ */ /** * Representation of a particular strategy. * * <p>This class is responsible for tracking the lifecycle and managing the execution * of a strategy. The existence of this object in scope represents the existence * of an actual strategy. If this object is <em>running</em> as indicated by * {@link #isRunning()}, then the embedded strategy is running. * * <p>The embedded strategy will not begin executing until {@link #start()} is invoked. * The strategy will continue to execute until stopped or an error occurs. * * <p>To make the embedded strategy stop, invoke {@link #stop()} (preferable) or allow * this object to go out-of-scope. If the object is allowed to go out-of-scope without * invoking {@link #stop()}, the embedded strategy will not be warned it is stopping. * Additionally, the strategy will keep executing until the next garbage-collection, * which is not deterministic. It is good practice to call {@link #stop()} on each * strategy at the appropriate time. * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: StrategyImpl.java 16667 2013-08-27 19:28:48Z colin $ * @since 1.0.0 */ @ClassVersion("$Id: StrategyImpl.java 16667 2013-08-27 19:28:48Z colin $") class StrategyImpl implements Strategy, Messages { /* (non-Javadoc) * @see org.marketcetera.strategy.Strategy#start() */ @Override public final void start() throws StrategyException { try { setExecutor(getLanguage().getExecutor(this)); setStatus(COMPILING); getExecutor().start(); // intentionally not setting status to "RUNNING" because the // "onStart" method, successful completion of which is required // to be in "RUNNING" status, is being executed elsewhere. // it is the responsibility of the "onStart" executor to determine // the status of the strategy - except in the case where an exception // is thrown initializing the execution of "onStart" - this is caught // below } catch (Exception e) { setStatus(FAILED); throw new StrategyException(e); } } /* (non-Javadoc) * @see org.marketcetera.strategy.Strategy#stop() */ @Override public final void stop() throws Exception { // if the strategy is at FAILED or STOPPED, this is not an error case to now try to stop it, but nothing // more needs (or is allowed) to be done (and the status should not change) if(getStatus().equals(FAILED) || getStatus().equals(STOPPED)) { return; } try { setStatus(STOPPING); getExecutor().stop(); // intentionally not setting status to "STOPPED" because the // "onStop" method, successful completion of which is required // to be in "STOPPED" status, is being executed elsewhere. // it is the responsibility of the "onStop" executor to determine // the status of the strategy - except in the case where an exception // is thrown initializing the execution of "onStop" - this is caught // below } catch (Exception e) { setStatus(FAILED); throw e; } } /* (non-Javadoc) * @see org.marketcetera.strategy.Strategy#dataReceived(java.lang.Object) */ @Override public final void dataReceived(Object inData) { // make sure that the strategy is in a state to receive incoming data if(!getStatus().canReceiveData()) { StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_STATUS_TO_RECEIVE_DATA, String.valueOf(this), String.valueOf(inData), getStatus()).create(), this); return; } String method = "onOther"; //$NON-NLS-1$ try { RunningStrategy runningStrategy = getRunningStrategy(); if(runningStrategy == null) { StrategyModule.log(LogEventBuilder.warn().withMessage(STRATEGY_NOT_READY_TO_RECEIVE_DATA, String.valueOf(this), String.valueOf(inData)).create(), this); return; } if(inData instanceof AskEvent) { method = "onAsk"; //$NON-NLS-1$ runningStrategy.onAsk((AskEvent)inData); return; } if(inData instanceof BidEvent) { method = "onBid"; //$NON-NLS-1$ runningStrategy.onBid((BidEvent)inData); return; } if(inData instanceof MarketstatEvent) { method = "onMarketstat"; //$NON-NLS-1$ runningStrategy.onMarketstat((MarketstatEvent)inData); return; } if(inData instanceof DividendEvent) { method = "onDividend"; //$NON-NLS-1$ runningStrategy.onDividend((DividendEvent)inData); return; } if(inData instanceof OrderCancelReject) { method = "onCancelReject"; //$NON-NLS-1$ if(runningStrategy instanceof AbstractRunningStrategy) { ((AbstractRunningStrategy)runningStrategy).onCancelRejectRedirected((OrderCancelReject)inData); } else { runningStrategy.onCancelReject((OrderCancelReject)inData); } return; } if(inData instanceof ExecutionReport) { method = "onExecutionReport"; //$NON-NLS-1$ if(runningStrategy instanceof AbstractRunningStrategy) { ((AbstractRunningStrategy)runningStrategy).onExecutionReportRedirected((ExecutionReport)inData); } else { runningStrategy.onExecutionReport((ExecutionReport)inData); } return; } if(inData instanceof TradeEvent) { method = "onTrade"; //$NON-NLS-1$ runningStrategy.onTrade((TradeEvent)inData); return; } // catch-all for every other type of data runningStrategy.onOther(inData); } catch (Exception e) { Executor executor = getExecutor(); String methodName = method; String exceptionTranslation = e.toString(); if(executor != null) { methodName = getExecutor().translateMethodName(method); exceptionTranslation = getExecutor().interpretRuntimeException(e); } StrategyModule.log(LogEventBuilder.warn().withMessage(RUNTIME_ERROR, String.valueOf(this), methodName, exceptionTranslation) .withException(e).create(), this); } } /* (non-Javadoc) * @see org.marketcetera.strategy.Strategy#getCode() */ @Override public final String getScript() { return code; } /* (non-Javadoc) * @see org.marketcetera.strategy.Strategy#getLanguage() */ @Override public final Language getLanguage() { return language; } /* (non-Javadoc) * @see org.marketcetera.strategy.Strategy#getName() */ @Override public final String getName() { return name; } /* (non-Javadoc) * @see org.marketcetera.strategy.Strategy#getParameters() */ @Override public final Properties getParameters() { return parameters; } /* (non-Javadoc) * @see org.marketcetera.strategy.Strategy#getStatus() */ @Override public final Status getStatus() { return status; } /** * Get the services provider value. * * @return a <code>ServicesProvider</code> value */ @Override public final ServicesProvider getServicesProvider() { return servicesProvider; } /* (non-Javadoc) * @see org.marketcetera.strategy.Strategy#getDefaultNamespace() */ @Override public final String getDefaultNamespace() { return defaultNamespace; } /* (non-Javadoc) * @see org.marketcetera.strategy.Strategy#getExecutor() */ @Override public final Executor getExecutor() { return executor; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public final String toString() { if(description == null) { description = String.format("%s Strategy %s(%s)", //$NON-NLS-1$ getLanguage().toString(), getName(), getUniqueIdentifier()); } return description; } /** * Create a new StrategyImpl instance. * * @param inName a <code>String</code> value * @param inUniqueIdentifier a <code>String</code> value * @param inType a <code>Language</code> value * @param inSource a <code>File</code> value * @param inParameters a <code>Properties</code> value * @param inNamespace a <code>String</code> value * @param inServicesProvider an <code>OutboundServices</code> value * @param inInboundServicesProvider an <code>InboundServices</code> value * @throws IOException if the given <code>File</code> could not be resolved */ StrategyImpl(String inName, String inUniqueIdentifier, Language inType, File inSource, Properties inParameters, String inNamespace, ServicesProvider inServicesProvider) throws IOException { status = UNSTARTED; name = inName; uniqueIdentifier = inUniqueIdentifier; language = inType; source = inSource; if(inParameters == null) { parameters = new Properties(); } else { parameters = new Properties(inParameters); } servicesProvider = inServicesProvider; if(source == null) { code = null; } else { code = fileToString(getSource()); } defaultNamespace = inNamespace; } /** * Get the uniqueIdentifier value. * * @return a <code>String</code> value */ final String getUniqueIdentifier() { return uniqueIdentifier; } /** * Get the source value. * * @return a <code>File</code> value */ final File getSource() { return source; } /** * Get the runningStrategy value. * * @return a <code>RunningStrategy</code> value */ final RunningStrategy getRunningStrategy() { return runningStrategy; } /** * Returns all currently running strategies. * * @return a <code>Set<StrategyImpl></code> value */ static Set<StrategyImpl> getRunningStrategies() { synchronized(runningStrategies) { return new HashSet<StrategyImpl>(runningStrategies); } } /** * Sets the status of the strategy. * * @param inStatus a <code>Status</code> value */ final void setStatus(Status inStatus) { assert(status.canChangeStatusTo(inStatus)); Status oldStatus = status; status = inStatus; // update the running strategy collection if(status.isRunning()) { synchronized(runningStrategies) { runningStrategies.add(this); } } else { synchronized(runningStrategies) { runningStrategies.remove(this); } } // notify that the status has changed getServicesProvider().statusChanged(oldStatus, inStatus); } /** * Sets the runningStrategy value. * * @param a <code>RunningStrategy</code> value */ final void setRunningStrategy(RunningStrategy inRunningStrategy) { runningStrategy = inRunningStrategy; } /** * Sets the executor value. * * @param an <code>Executor</code> value */ private void setExecutor(Executor inExecutor) { executor = inExecutor; } /** * Reads the given <code>File</code> and renders its contents as a <code>String</code>. * * @param inFile a <code>File</code> value * @return a <code>String</code> value * @throws IOException if the <code>File</code> can not be read */ private String fileToString(File inFile) throws IOException { return FileUtils.readFileToString(inFile); } /** * all strategies that are in RUNNING state */ private static final Set<StrategyImpl> runningStrategies = new HashSet<StrategyImpl>(); /** * the user-applied name of the strategy. this name has no strict correlation to any artifact declared by the embedded strategy itself. */ final String name; /** * the type of the strategy being executed */ private final Language language; /** * a reference to the actual code of the strategy */ private final File source; /** * the actual code of the strategy */ private final String code; /** * the set of parameters to pass to the strategy. some of the values contained within may be meta-data that is relevant to the strategy manager (this object) rather than the strategy itself. */ private final Properties parameters; /** * the provider of services via the strategy agent framework */ private final ServicesProvider servicesProvider; /** * the default namespace for this strategy */ private final String defaultNamespace; /** * the value that uniquely identifies this strategy to the system within the scope of this JVM execution */ private final String uniqueIdentifier; /** * the executor responsible for execution of this strategy */ private Executor executor; /** * interface to the embedded running strategy object - this object is created by the execution engine */ private RunningStrategy runningStrategy; /** * the strategy status */ private Status status; /** * description of this object initialized when needed */ private String description; }