package org.marketcetera.strategyagent;
import java.io.IOException;
import java.io.LineNumberReader;
import java.lang.management.ManagementFactory;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.management.JMX;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.apache.commons.lang.Validate;
import org.marketcetera.core.ApplicationContainer;
import org.marketcetera.core.ApplicationVersion;
import org.marketcetera.core.publisher.IPublisher;
import org.marketcetera.core.publisher.ISubscriber;
import org.marketcetera.core.publisher.PublisherEngine;
import org.marketcetera.module.*;
import org.marketcetera.util.except.I18NException;
import org.marketcetera.util.misc.ClassVersion;
import org.marketcetera.util.unicode.UnicodeFileReader;
import org.marketcetera.util.ws.stateful.Authenticator;
import org.marketcetera.util.ws.stateful.Server;
import org.marketcetera.util.ws.stateless.ServiceInterface;
import org.marketcetera.util.ws.stateless.StatelessClientContext;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.Lifecycle;
/* $License$ */
/**
* The main class for the strategy agent.
* <p>
* This class starts off the
* module container and then reads up a list of module manager commands
* from a command file and executes it and waits until it's killed.
* <p>
* If no commands file is supplied, the strategy agent does nothing, it
* simply waits until it's killed.
* <p>
* After the strategy agent is started, clients can connect to it via
* JMX and manage it via the {@link org.marketcetera.module.ModuleManagerMXBean}
* interface.
*
* @author anshul@marketcetera.com
*/
@ClassVersion("$Id: StrategyAgent.java 16870 2014-04-03 01:58:49Z colin $")
public class StrategyAgent
implements IPublisher,Lifecycle,ApplicationContextAware
{
/**
* Gets the most recently created <code>StrategyAgent</code> instance in this process.
*
* @return a <code>StrategyAgent</code> value
*/
public static StrategyAgent getInstance()
{
return instance;
}
/**
* Get the contextClasses value.
*
* @return a <code>Class<?>[]</code> value
*/
public Class<?>[] getContextClasses()
{
return contextClasses;
}
/**
* Sets the contextClasses value.
*
* @param a <code>Class<?>[]</code> value
*/
public void setContextClasses(Class<?>[] inContextClasses)
{
if(isRunning()) {
throw new IllegalStateException();
}
contextClasses = inContextClasses;
}
/* (non-Javadoc)
* @see org.marketcetera.core.publisher.IPublisher#subscribe(org.marketcetera.core.publisher.ISubscriber)
*/
@Override
public void subscribe(ISubscriber inSubscriber)
{
dataPublisher.subscribe(inSubscriber);
}
/* (non-Javadoc)
* @see org.marketcetera.core.publisher.IPublisher#unsubscribe(org.marketcetera.core.publisher.ISubscriber)
*/
@Override
public void unsubscribe(ISubscriber inSubscriber)
{
dataPublisher.unsubscribe(inSubscriber);
}
/* (non-Javadoc)
* @see org.marketcetera.core.publisher.IPublisher#publish(java.lang.Object)
*/
@Override
public void publish(Object inData)
{
dataPublisher.publish(inData);
}
/* (non-Javadoc)
* @see org.marketcetera.core.publisher.IPublisher#publishAndWait(java.lang.Object)
*/
@Override
public void publishAndWait(Object inData)
throws InterruptedException, ExecutionException
{
dataPublisher.publishAndWait(inData);
}
/**
* Get the dataPublisher value.
*
* @return a <code>PublisherEngine</code> value
*/
public PublisherEngine getDataPublisher()
{
return dataPublisher;
}
/**
* Sets the dataPublisher value.
*
* @param inDataPublisher a <code>PublisherEngine</code> value
*/
public void setDataPublisher(PublisherEngine inDataPublisher)
{
if(isRunning()) {
throw new IllegalStateException();
}
dataPublisher = inDataPublisher;
}
/**
* Get the authenticator value.
*
* @return an <code>Authenticator</code> value
*/
public Authenticator getAuthenticator()
{
return authenticator;
}
/**
* Sets the authenticator value.
*
* @param inAuthenticator an <code>Authenticator</code> value
*/
public void setAuthenticator(Authenticator inAuthenticator)
{
authenticator = inAuthenticator;
}
/**
* Stops the strategy agent.
*/
public void stop()
{
stopRemoteService();
}
/* (non-Javadoc)
* @see org.springframework.context.Lifecycle#isRunning()
*/
@Override
public boolean isRunning()
{
return running.get();
}
/* (non-Javadoc)
* @see org.springframework.context.Lifecycle#start()
*/
@Override
public void start()
{
Messages.LOG_APP_VERSION_BUILD.info(this,
ApplicationVersion.getVersion(),
ApplicationVersion.getBuildNumber());
Validate.notNull(authenticator);
if(loader != null) {
Thread.currentThread().setContextClassLoader(loader);
}
if(dataPublisher == null) {
dataPublisher = new PublisherEngine();
}
try {
//Configure the application. If it fails, exit
String[] args = ApplicationContainer.getInstance().getArguments();
if(args != null && args.length > 0) {
int parseErrors = parseCommands(args[0]);
if(parseErrors > 0) {
Messages.LOG_COMMAND_PARSE_ERRORS.error(StrategyAgent.class,
parseErrors);
throw new IllegalArgumentException(Messages.LOG_COMMAND_PARSE_ERRORS.getText(parseErrors));
}
}
} catch(Exception e) {
Messages.LOG_ERROR_CONFIGURE_AGENT.error(StrategyAgent.class,
getMessage(e));
Messages.LOG_ERROR_CONFIGURE_AGENT.debug(StrategyAgent.class,
e,
getMessage(e));
throw new RuntimeException(e);
}
// initialize the application. If it fails, exit
try {
init();
} catch (Exception e) {
Messages.LOG_ERROR_INITIALIZING_AGENT.error(StrategyAgent.class,
getMessage(e));
Messages.LOG_ERROR_INITIALIZING_AGENT.debug(StrategyAgent.class,
e,
getMessage(e));
throw new RuntimeException(e);
}
// run the commands
executeCommands();
running.set(true);
}
/**
* Get the moduleManager value.
*
* @return a <code>ModuleManager</code> value
*/
public ModuleManager getModuleManager()
{
return moduleManager;
}
/**
* Sets the moduleManager value.
*
* @param inModuleManager a <code>ModuleManager</code> value
*/
public void setModuleManager(ModuleManager inModuleManager)
{
if(isRunning()) {
throw new IllegalStateException();
}
moduleManager = inModuleManager;
}
/* (non-Javadoc)
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
@Override
public void setApplicationContext(ApplicationContext inApplicationContext)
throws BeansException
{
applicationContext = inApplicationContext;
}
/**
* Get the applicationContext value.
*
* @return an <code>ApplicationContext</code> value
*/
public ApplicationContext getApplicationContext()
{
return applicationContext;
}
/* (non-Javadoc)
* @see org.marketcetera.core.publisher.IPublisher#getSubscriptionCount()
*/
@Override
public int getSubscriptionCount()
{
return dataPublisher.getSubscriptionCount();
}
/**
* Get the loader value.
*
* @return a <code>ClassLoader</code> value
*/
public ClassLoader getLoader()
{
return loader;
}
/**
* Sets the loader value.
*
* @param inLoader a <code>ClassLoader</code> value
*/
public void setLoader(ClassLoader inLoader)
{
if(isRunning()) {
throw new IllegalStateException();
}
loader = inLoader;
}
/**
* Initializes the module manager.
*
* @throws ModuleException if there were errors initializing the module
* manager.
* @throws MalformedObjectNameException if there were errors creating
* the object name of the module manager bean.
*/
private void init()
throws ModuleException, MalformedObjectNameException
{
//Initialize the module manager.
if(moduleManager != null) {
moduleManager.init();
//Add the logger sink listener
moduleManager.addSinkListener(new SinkDataListener() {
public void receivedData(DataFlowID inFlowID, Object inData) {
final boolean isNullData = inData == null;
Messages.LOG_SINK_DATA.info(SINK_DATA, inFlowID,
isNullData ? 0: 1,
isNullData ? null: inData.getClass().getName(),
inData);
}
});
mManagerBean = JMX.newMXBeanProxy(ManagementFactory.getPlatformMBeanServer(),
new ObjectName(ModuleManager.MODULE_MBEAN_NAME),
ModuleManagerMXBean.class);
}
}
/**
* Create a new StrategyAgent instance.
*/
public StrategyAgent()
{
instance = this;
}
/**
* Stops the remote web service.
*/
private void stopRemoteService() {
if(mRemoteService != null) {
mRemoteService.stop();
mRemoteService = null;
}
if(mServer != null) {
mServer.stop();
mServer = null;
}
}
/**
* Return the exception message from the supplied Throwable.
*
* @param inThrowable the throwable whose message needs to be returned.
*
* @return the throwable message.
*/
private static String getMessage(Throwable inThrowable) {
if(inThrowable instanceof I18NException) {
return ((I18NException)inThrowable).getLocalizedDetail();
} else {
return inThrowable.getLocalizedMessage();
}
}
/**
* Parses the commands from the supplied commands file.
*
* @param inFile the file path
*
* @throws IOException if there were errors parsing the file.
*
* @return the number of errors encountered when parsing the command file.
*/
private int parseCommands(String inFile) throws IOException {
int numErrors = 0;
LineNumberReader reader = new LineNumberReader(
new UnicodeFileReader(inFile));
try {
String line;
while((line = reader.readLine()) != null) {
if(line.startsWith("#") || line.trim().isEmpty()) { //$NON-NLS-1$
//Ignore comments and empty lines.
continue;
}
int idx = line.indexOf(';'); //$NON-NLS-1$
if(idx > 0) {
String key = line.substring(0, idx);
CommandRunner runner = sRunners.get(key);
if(runner == null) {
numErrors++;
Messages.INVALID_COMMAND_NAME.error(this, key,
reader.getLineNumber());
continue;
}
mCommands.add(new Command(runner, line.substring(++idx),
reader.getLineNumber()));
} else {
numErrors++;
Messages.INVALID_COMMAND_SYNTAX.error(this,
line, reader.getLineNumber());
}
}
return numErrors;
} finally {
reader.close();
}
}
/**
* Authenticates a client connection.
* <p>
* This method is package protected to enable its unit testing.
*
* @param context the client's context.
* @param user the user name.
* @param password the password.
*
* @return if the authentication succeeded.
*
* @throws I18NException if the client is incompatible with the server.
*/
boolean authenticate(StatelessClientContext context,
String user,
char[] password)
throws I18NException
{
return authenticator.shouldAllow(context,
user,
password);
}
/**
* Executes commands, if any were provided. If any command fails, the
* failure is logged. Failure of any command doesn't prevent the next
* command from executing or prevent the application from exiting.
*/
private void executeCommands() {
if(!mCommands.isEmpty()) {
for(Command c: mCommands) {
try {
Messages.LOG_RUNNING_COMMAND.info(this,
c.getRunner().getName(), c.getParameter());
Object result = c.getRunner().runCommand(
mManagerBean, c.getParameter());
Messages.LOG_COMMAND_RUN_RESULT.info(this,
c.getRunner().getName(), result);
} catch (Exception e) {
Messages.LOG_ERROR_EXEC_CMD.warn(this,
c.getRunner().getName(),
c.getParameter(), c.getLineNum(),
getMessage(e));
Messages.LOG_ERROR_EXEC_CMD.debug(this, e,
c.getRunner().getName(),
c.getParameter(), c.getLineNum(),
getMessage(e));
}
}
}
}
/**
* Adds a command runner instance to the table of command runners.
*
* @param inRunner the command runner to be added.
*/
private static void addRunner(CommandRunner inRunner) {
sRunners.put(inRunner.getName(), inRunner);
}
/**
* The log category used to log all the data received by the sink module
*/
public static final String SINK_DATA = "SINK"; //$NON-NLS-1$
/**
* Exit code when the command exits because of parsing errors
*/
public static final int EXIT_CMD_PARSE_ERROR = 1;
/**
* Exit code when command exits because of errors starting up.
*/
public static final int EXIT_START_ERROR = 2;
/**
* Exit code when command exits because of initialization errors.
*/
public static final int EXIT_INIT_ERROR = 3;
/**
* The table of command names and command runners.
*/
private static final Map<String, CommandRunner> sRunners =
new HashMap<String, CommandRunner>();
static {
//Initialize the set of available command runners
addRunner(new CreateModule());
addRunner(new CreateDataFlow());
addRunner(new StartModule());
}
/**
* The module manager instance.
*/
private ModuleManager moduleManager;
/**
* SA class loader value
*/
private ClassLoader loader;
/**
* The module manager mxbean reference.
*/
private ModuleManagerMXBean mManagerBean;
/**
* The list of parsed commands.
*/
private List<Command> mCommands = new LinkedList<Command>();
/**
* The handle to the remote web service.
*/
private volatile ServiceInterface mRemoteService;
/**
* web services provider server
*/
private volatile Server<ClientSession> mServer;
/**
* used to publish data received to interested subscribers
*/
private volatile PublisherEngine dataPublisher;
/**
* extra context classes to add to the server context
*/
private volatile Class<?>[] contextClasses;
/**
* most recent strategy agent instance
*/
private volatile static StrategyAgent instance;
/**
* indicates if the SA is running or not
*/
private final AtomicBoolean running = new AtomicBoolean(false);
/**
* provides authentication services
*/
private Authenticator authenticator = new DefaultAuthenticator();
/**
* application context
*/
private ApplicationContext applicationContext;
}