package org.marketcetera.photon;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.Date;
import java.util.logging.LogManager;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.*;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import org.marketcetera.core.ClassVersion;
import org.marketcetera.core.instruments.UnderlyingSymbolSupport;
import org.marketcetera.core.position.PositionEngine;
import org.marketcetera.messagehistory.TradeReportsHistory;
import org.marketcetera.photon.core.ICredentialsService;
import org.marketcetera.photon.core.ILogoutService;
import org.marketcetera.photon.core.ISymbolResolver;
import org.marketcetera.photon.marketdata.IMarketDataManager;
import org.marketcetera.photon.marketdata.ui.ReconnectMarketDataJob;
import org.marketcetera.photon.preferences.PhotonPage;
import org.marketcetera.photon.views.*;
import org.marketcetera.quickfix.*;
import org.marketcetera.strategy.Strategy;
import org.marketcetera.trade.*;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
import org.rubypeople.rdt.core.RubyCore;
/* $License$ */
/**
* The main plugin class to be used in the Photon application. This class is not
* synchronized and should only be accessed from the UI thread.
*
* @version $Id: PhotonPlugin.java 16854 2014-03-12 01:54:42Z colin $
* @since 1.0.0
*/
@ClassVersion("$Id: PhotonPlugin.java 16854 2014-03-12 01:54:42Z colin $")
public class PhotonPlugin extends AbstractUIPlugin implements Messages,
IPropertyChangeListener {
public static final String ID = "org.marketcetera.photon"; //$NON-NLS-1$
// The shared instance.
private static PhotonPlugin plugin;
private TradeReportsHistory mTradeReportsHistory;
/**
* Cannot be initialized until after logging infrastructure is set up
*/
private Logger mainConsoleLogger;
private PhotonController photonController;
private BundleContext bundleContext;
public static final String MAIN_CONSOLE_LOGGER_NAME = "main.console.logger"; //$NON-NLS-1$
public static final String STRATEGY_LOGGER_NAME = org.marketcetera.core.Messages.USER_MSG_CATEGORY;
public static final String DEFAULT_PROJECT_NAME = "ActiveScripts"; //$NON-NLS-1$
private static final String RUBY_NATURE_ID = ".rubynature"; //$NON-NLS-1$
private FIXMessageFactory messageFactory;
private FIXVersion fixVersion;
private SecondaryIDCreator secondaryIDCreator = new SecondaryIDCreator();
private StockOrderTicketModel stockOrderTicketModel;
private OptionOrderTicketModel optionOrderTicketModel;
private FutureOrderTicketModel futureOrderTicketModel;
private CurrencyOrderTicketModel currencyOrderTicketModel;
private StockOrderTicketController stockOrderTicketController;
private OptionOrderTicketController optionOrderTicketController;
private FutureOrderTicketController futureOrderTicketController;
private CurrencyOrderTicketController currencyOrderTicketController;
private BrokerManager mBrokerManager;
private PositionEngine mPositionEngine;
private SessionStartTimeProvider mSessionStartTimeProvider = new SessionStartTimeProvider();
private ServiceRegistration<?> mPositionEngineService;
private ServiceTracker<?,?> mMarketDataManagerTracker;
private ServiceTracker<?,?> mCredentialsServiceTracker;
private ServiceTracker<?,?> mLogoutServiceTracker;
private final UnderlyingSymbolSupport mUnderlyingSymbolSupport = new ClientUnderlyingSymbolSupport();
private ServiceTracker<?,?> mSymbolResolverServiceTracker;
private Job mReconnectMarketDataFeedJob;
/**
* The constructor.
*/
public PhotonPlugin() {
plugin = this;
}
/**
* This method is called upon plug-in activation
*/
public void start(BundleContext context) throws Exception {
super.start(context);
bundleContext = context;
configureLogs();
mainConsoleLogger = Logger.getLogger(MAIN_CONSOLE_LOGGER_NAME);
new DefaultScope().getNode("org.rubypeople.rdt.launching").putBoolean("org.rubypeople.rdt.launching.us.included.jruby", true); //$NON-NLS-1$ //$NON-NLS-2$
String level = getPreferenceStore().getString(
PhotonPreferences.CONSOLE_LOG_LEVEL);
changeLogLevel(level == null ? PhotonPage.LOG_LEVEL_VALUE_INFO : level);
// This sets the internal broker to use on thread per "listener"?
// Needed because the version of JRuby we're using doesn't play well
// with mutliple threads
// TODO: is this still needed??
System.setProperty("org.apache.activemq.UseDedicatedTaskRunner", "true"); //$NON-NLS-1$ //$NON-NLS-2$
initMessageFactory();
initTradeReportsHistory();
initPhotonController();
PhotonPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
mMarketDataManagerTracker = new ServiceTracker<Object,Object>(context,
IMarketDataManager.class.getName(),
null);
mMarketDataManagerTracker.open();
mCredentialsServiceTracker = new ServiceTracker<Object,Object>(context,
ICredentialsService.class.getName(),
null);
mCredentialsServiceTracker.open();
mLogoutServiceTracker = new ServiceTracker<Object,Object>(context,
ILogoutService.class.getName(),
null);
mLogoutServiceTracker.open();
mSymbolResolverServiceTracker = new ServiceTracker<Object,Object>(context,
ISymbolResolver.class.getName(),
null);
mSymbolResolverServiceTracker.open();
context.registerService(UnderlyingSymbolSupport.class.getName(),
mUnderlyingSymbolSupport,
null);
}
public void initOrderTickets() {
stockOrderTicketModel = new StockOrderTicketModel();
optionOrderTicketModel = new OptionOrderTicketModel();
futureOrderTicketModel = new FutureOrderTicketModel();
currencyOrderTicketModel = new CurrencyOrderTicketModel();
stockOrderTicketController = new StockOrderTicketController(
stockOrderTicketModel);
optionOrderTicketController = new OptionOrderTicketController(
optionOrderTicketModel);
futureOrderTicketController = new FutureOrderTicketController(futureOrderTicketModel);
currencyOrderTicketController = new CurrencyOrderTicketController(currencyOrderTicketModel);
}
private void initPhotonController() {
photonController = new PhotonController();
photonController.setMessageHistory(mTradeReportsHistory);
photonController.setMainConsoleLogger(getMainConsoleLogger());
}
private void initTradeReportsHistory() {
mTradeReportsHistory = new TradeReportsHistory(messageFactory, mUnderlyingSymbolSupport);
}
/**
* Returns the {@link UnderlyingSymbolSupport}.
*
* @return the underlying symbol support
*/
public UnderlyingSymbolSupport getUnderlyingSymbolSupport() {
return mUnderlyingSymbolSupport;
}
/**
* Gets the symbol resolver value.
*
* @return an <code>ISymbolResolver</code> value
*/
public ISymbolResolver getSymbolResolver()
{
return (ISymbolResolver)mSymbolResolverServiceTracker.getService();
}
/**
* This method is called when the plug-in is stopped
*/
public void stop(BundleContext context) throws Exception {
super.stop(context);
plugin = null;
mPositionEngine = null;
if(mMarketDataManagerTracker != null) {
IMarketDataManager marketDataManager = getMarketDataManager();
marketDataManager.close();
mMarketDataManagerTracker.close();
mMarketDataManagerTracker = null;
}
if (mCredentialsServiceTracker != null) {
mCredentialsServiceTracker.close();
mCredentialsServiceTracker = null;
}
if (mLogoutServiceTracker != null) {
mLogoutServiceTracker.close();
mLogoutServiceTracker = null;
}
if(mReconnectMarketDataFeedJob != null){
mReconnectMarketDataFeedJob.cancel();
mReconnectMarketDataFeedJob = null;
}
}
/**
* Returns the shared instance.
*/
public static PhotonPlugin getDefault() {
return plugin;
}
/**
* Returns an image descriptor for the image file at the given plug-in
* relative path.
*
* @param path
* the path
* @return the image descriptor
*/
public static ImageDescriptor getImageDescriptor(String path) {
return AbstractUIPlugin.imageDescriptorFromPlugin(ID, path);
}
@Override
public ScopedPreferenceStore getPreferenceStore() {
return (ScopedPreferenceStore) super.getPreferenceStore();
}
private void initMessageFactory() throws FIXFieldConverterNotAvailable {
fixVersion = FIXVersion.FIX_SYSTEM;
messageFactory = fixVersion.getMessageFactory();
CurrentFIXDataDictionary
.setCurrentFIXDataDictionary(FIXDataDictionaryManager
.initialize(fixVersion, fixVersion
.getDataDictionaryURL()));
}
/**
* Accessor for the console logger singleton. This logger writes messages
* into the main console displayed to the user in the application.
*
* @return the main console logger
*/
public Logger getMainLogger() {
return mainConsoleLogger;
}
public static Logger getMainConsoleLogger() {
return getDefault().getMainLogger();
}
/**
* Accessor for the TradeReportsHistory singleton.
*
* @return the TradeReportsHistory singleton
*/
public TradeReportsHistory getTradeReportsHistory() {
return mTradeReportsHistory;
}
/**
* Accessor for the OrderManager singleton. The OrderManager is the holder
* of most of the business logic for the application.
*
* @return the order manager singleton
*/
public PhotonController getPhotonController() {
return photonController;
}
public BundleContext getBundleContext() {
return bundleContext;
}
public void ensureDefaultProject(IProgressMonitor monitor) {
monitor.beginTask("Ensure default project", 2); //$NON-NLS-1$
try {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot root = workspace.getRoot();
IProject newProject = root.getProject(DEFAULT_PROJECT_NAME);
IProjectDescription description = workspace
.newProjectDescription(newProject.getName());
if (!newProject.exists()) {
newProject.create(description, new SubProgressMonitor(monitor,
1));
}
// this is the full path of the "ActiveScripts" directory - pass
// this in by default to Ruby scripts in order to pick up
// any other scripts in the same dir for "requires" directives
String determineClasspath = ResourcesPlugin.getWorkspace()
.getRoot().getProject(DEFAULT_PROJECT_NAME).getLocation()
.toString();
System.setProperty(Strategy.CLASSPATH_PROPERTYNAME,
determineClasspath);
if (!newProject.isOpen()) {
newProject.open(monitor);
}
try {
if (!newProject.hasNature(RUBY_NATURE_ID)) {
try {
RubyCore.addRubyNature(newProject,
new SubProgressMonitor(monitor, 1));
} catch (Throwable t) {
// RDT possibly not included...
mainConsoleLogger.error(CANNOT_LOAD_RUBY.getText(), t);
}
}
} catch (CoreException e) {
if (mainConsoleLogger.isDebugEnabled())
mainConsoleLogger
.debug(
"Exception trying to determine nature of default project.", //$NON-NLS-1$
e);
}
} catch (Throwable t) {
mainConsoleLogger.error(CANNOT_START_DEFAULT_SCRIPT_PROJECT
.getText(), t);
}
monitor.done();
}
public void changeLogLevel(String levelValue) {
Logger strategyLogger = Logger.getLogger(STRATEGY_LOGGER_NAME);
if (PhotonPage.LOG_LEVEL_VALUE_ERROR.equals(levelValue)) {
mainConsoleLogger.setLevel(Level.ERROR);
strategyLogger.setLevel(Level.ERROR);
} else if (PhotonPage.LOG_LEVEL_VALUE_WARN.equals(levelValue)) {
mainConsoleLogger.setLevel(Level.WARN);
strategyLogger.setLevel(Level.WARN);
} else if (PhotonPage.LOG_LEVEL_VALUE_INFO.equals(levelValue)) {
mainConsoleLogger.setLevel(Level.INFO);
strategyLogger.setLevel(Level.INFO);
} else if (PhotonPage.LOG_LEVEL_VALUE_DEBUG.equals(levelValue)) {
mainConsoleLogger.setLevel(Level.DEBUG);
strategyLogger.setLevel(Level.DEBUG);
}
mainConsoleLogger.info(LOGGER_LEVEL_CHANGED.getText(levelValue));
}
public FIXMessageFactory getMessageFactory() {
return messageFactory;
}
public FIXDataDictionary getFIXDataDictionary() {
return CurrentFIXDataDictionary.getCurrentFIXDataDictionary();
}
public FIXVersion getFIXVersion() {
return fixVersion;
}
public static IViewPart getActiveView(String viewId) {
IWorkbenchWindow activeWindow = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow();
if (activeWindow != null) {
IWorkbenchPage activePage = activeWindow.getActivePage();
if (activePage != null) {
return activePage.findView(viewId);
}
}
return null;
}
/**
* @return a view of the expectedClass. null if not found or the found view
* is not of the expected class.
*/
public static IViewPart getActiveView(String viewId, Class<?> expectedClass) {
IViewPart viewPart = getActiveView(viewId);
if (viewPart == null) {
return null;
}
if (expectedClass.isAssignableFrom(viewPart.getClass())) {
return viewPart;
}
return null;
}
public StockOrderTicketModel getStockOrderTicketModel() {
return stockOrderTicketModel;
}
public OptionOrderTicketModel getOptionOrderTicketModel() {
return optionOrderTicketModel;
}
public FutureOrderTicketModel getFutureOrderTicketModel()
{
return futureOrderTicketModel;
}
public CurrencyOrderTicketModel getCurrencyOrderTicketModel() {
return currencyOrderTicketModel;
}
public StockOrderTicketController getStockOrderTicketController() {
return stockOrderTicketController;
}
public OptionOrderTicketController getOptionOrderTicketController() {
return optionOrderTicketController;
}
public FutureOrderTicketController getFutureOrderTicketController()
{
return futureOrderTicketController;
}
public CurrencyOrderTicketController getCurrencyOrderTicketController() {
return currencyOrderTicketController;
}
/**
* Returns the order ticket appropriate for the given order.
*
* @param order
* the order
* @return the controller for the appropriate order ticket.
*/
public IOrderTicketController getOrderTicketController(NewOrReplaceOrder order) {
// TODO: instrument specific functionality that can be abstracted
Instrument i = order.getInstrument();
if (i instanceof Option) {
return getOptionOrderTicketController();
}
if(i instanceof Future) {
return getFutureOrderTicketController();
}
if(i instanceof Currency) {
return getCurrencyOrderTicketController();
}
return getStockOrderTicketController();
}
/**
* Navigates to the appropriate perspective and sets the order on the ticket
* view.
*
* @param order
* the order
* @throws WorkbenchException
* if an error occurs switching to the view
*/
public void showOrderInTicket(NewOrReplaceOrder order)
throws WorkbenchException {
IOrderTicketController orderTicketController = getOrderTicketController(order);
String view = orderTicketController.getViewId();
orderTicketController.setOrderMessage(order);
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
.showView(view);
}
/**
* @return the next secondary ID for use in IWorkbenchPage.showView()
*/
public String getNextSecondaryID() {
return secondaryIDCreator.getNextSecondaryID();
}
/**
* Returns the {@link IMarketDataManager} service.
*
* @return the {@link IMarketDataManager} service
*/
public IMarketDataManager getMarketDataManager()
{
IMarketDataManager marketDataManager = (IMarketDataManager)mMarketDataManagerTracker.getService();
if(marketDataManager != null) {
marketDataManager.setCredentialsService(getCredentialsService());
ScopedPreferenceStore prefs = getPreferenceStore();
marketDataManager.setHostname(prefs.getString(PhotonPreferences.NEXUS_WEB_SERVICE_HOST));
marketDataManager.setPort(prefs.getInt(PhotonPreferences.NEXUS_WEB_SERVICE_PORT));
}
return (IMarketDataManager)mMarketDataManagerTracker.getService();
}
/**
* Returns the {@link ICredentialsService} service.
*
* @return the {@link ICredentialsService} service
*/
public ICredentialsService getCredentialsService() {
return (ICredentialsService) mCredentialsServiceTracker.getService();
}
/**
* Returns the {@link ILogoutService} service.
*
* @return the {@link ILogoutService} service
*/
public ILogoutService getLogoutService() {
return (ILogoutService) mLogoutServiceTracker.getService();
}
@Override
protected void initializeImageRegistry(ImageRegistry reg) {
PhotonImages.initializeSharedImages(reg);
}
/**
* The system property that is set to a unique number for every photon
* process. An attempt is made to use the pid value as the value of this
* property. However, if that doesn't work, the system time at the time this
* property is set, is set as the value of this property.
*/
private static final String PROCESS_UNIQUE_PROPERTY = "org.marketcetera.photon.unique"; //$NON-NLS-1$
/**
* log4j configuration file name.
*/
private static final String LOG4J_CONFIG = "photon-log4j.properties"; //$NON-NLS-1$
/**
* java logging configuration file name.
*/
private static final String JAVA_LOGGING_CONFIG = "java.util.logging.properties"; //$NON-NLS-1$
/**
* The system property name that contains photon installation directory
*/
private static final String APP_DIR_PROP = "org.marketcetera.appDir"; //$NON-NLS-1$
/**
* The configuration sub directory for the application
*/
private static final String CONF_DIR = "conf"; //$NON-NLS-1$
/**
* Delay for rereading log4j configuration.
*/
private static final int LOGGER_WATCH_DELAY = 20 * 1000;
/**
* Configure Logs
*
* @throws FileNotFoundException
* @throws IOException
*/
private void configureLogs() throws FileNotFoundException, IOException {
// Fetch the java process ID. Do note that this mechanism relies on
// a non-public interface of the jvm but its very useful to be able
// to use the pid.
String id = ManagementFactory.getRuntimeMXBean().getName().replaceAll(
"[^0-9]", ""); //$NON-NLS-1$ //$NON-NLS-2$
if (id == null || id.trim().length() < 1) {
id = String.valueOf(System.currentTimeMillis());
}
// Supply the pid as a system property so that it can be used in
// log 4j configuration
System.setProperty(PROCESS_UNIQUE_PROPERTY, id);
// Figure out if the application install dir is specified
String appDir = System.getProperty(APP_DIR_PROP);
File confDir = null;
// Configure loggers
if (appDir != null) {
File dir = new File(appDir, CONF_DIR);
if (dir.isDirectory()) {
confDir = dir;
}
}
// Configure Java Logging
boolean logConfigured = false;
if (confDir != null) {
File logConfig = new File(confDir, JAVA_LOGGING_CONFIG);
if (logConfig.isFile()) {
FileInputStream fis = null;
try {
fis = new FileInputStream(logConfig);
LogManager.getLogManager().readConfiguration(fis);
logConfigured = true;
} catch (Exception ignored) {
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException ignored) {
}
}
}
}
}
// Do default configuration, if its not already done.
if (!logConfigured) {
LogManager.getLogManager().readConfiguration(
getClass().getClassLoader().getResourceAsStream(
JAVA_LOGGING_CONFIG));
}
// Configure Log4j
// Remove default configuration done via log4j.properties file
// present in one of the jars that we depend on
BasicConfigurator.resetConfiguration();
logConfigured = false;
if (confDir != null) {
File logConfig = new File(confDir, LOG4J_CONFIG);
if (logConfig.isFile()) {
PropertyConfigurator.configureAndWatch(logConfig
.getAbsolutePath(), LOGGER_WATCH_DELAY);
logConfigured = true;
}
}
if (!logConfigured) {
// Do default log4j configuration, if its not already done.
PropertyConfigurator.configure(getClass().getClassLoader()
.getResource(LOG4J_CONFIG));
}
}
public void propertyChange(PropertyChangeEvent event) {
if (event.getProperty().equals(PhotonPreferences.CONSOLE_LOG_LEVEL)) {
PhotonPlugin.getDefault().changeLogLevel("" + event.getNewValue()); //$NON-NLS-1$
}
}
/**
* Returns the {@link BrokerManager} singleton for this plug-in. Typically,
* this should be accessed through {@link BrokerManager#getCurrent()}.
*
* @return the BrokerManager singleton for this plug-in
*/
public BrokerManager getBrokerManager() {
if (mBrokerManager == null) {
mBrokerManager = new BrokerManager();
}
return mBrokerManager;
}
/**
* Starts a background job to reconnect to the market data feed.
*/
public void reconnectMarketDataFeed()
{
ReconnectMarketDataJob marketDataJob = new ReconnectMarketDataJob();
marketDataJob.setMarketDataManager(getMarketDataManager());
marketDataJob.schedule();
}
/**
* Returns an object that provides the currently effective session start
* time. Unlike the PhotonPreferences.TRADING_HISTORY_START_TIME preference
* value which is just a time of day, this value is the exact date object
* that was used during the last connection to the server.
*
* @return the session start time provider
*/
public ISessionStartTimeProvider getSessionStartTimeProvider() {
return mSessionStartTimeProvider;
}
/**
* Sets the session start time.
*
* @see PhotonPlugin#getSessionStartTimeProvider()
*
* @param newSessionStartTime
* the new session start time
*/
public void setSessionStartTime(Date newSessionStartTime) {
mSessionStartTimeProvider.setSessionStartTime(newSessionStartTime);
}
/**
* Registers the position engine as an OSGi service.
*
* @param engine
* position engine to register
*/
public synchronized void registerPositionEngine(PositionEngine engine) {
mPositionEngine = engine;
mPositionEngineService = getBundleContext().registerService(
PositionEngine.class.getName(), engine, null);
}
/**
* Disposes the registered position engine server if one exists.
*/
public synchronized void disposePositionEngine() {
if (mPositionEngine != null) {
mPositionEngine.dispose();
mPositionEngine = null;
}
if (mPositionEngineService != null) {
mPositionEngineService.unregister();
mPositionEngineService = null;
}
}
}