/*
* Copyright (c) 2004-2011 Marco Maccaferri and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Marco Maccaferri - initial API and implementation
*/
package org.eclipsetrader.yahoo.internal.core.connector;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Status;
import org.eclipsetrader.core.feed.IConnectorListener;
import org.eclipsetrader.core.feed.IFeedConnector;
import org.eclipsetrader.core.feed.IFeedIdentifier;
import org.eclipsetrader.core.feed.IFeedSubscription;
import org.eclipsetrader.core.feed.LastClose;
import org.eclipsetrader.core.feed.Quote;
import org.eclipsetrader.core.feed.TodayOHL;
import org.eclipsetrader.core.feed.Trade;
import org.eclipsetrader.yahoo.internal.YahooActivator;
import org.eclipsetrader.yahoo.internal.core.Util;
import org.eclipsetrader.yahoo.internal.core.repository.IdentifierType;
import org.eclipsetrader.yahoo.internal.core.repository.IdentifiersList;
import org.eclipsetrader.yahoo.internal.core.repository.PriceDataType;
public class SnapshotConnector implements Runnable, IFeedConnector, IExecutableExtension, PropertyChangeListener {
private static final int I_CODE = 0;
private static final int I_LAST = 1;
private static final int I_DATE = 2;
private static final int I_TIME = 3;
//private static final int I_CHANGE = 4;
private static final int I_OPEN = 5;
private static final int I_HIGH = 6;
private static final int I_LOW = 7;
private static final int I_VOLUME = 8;
private static final int I_BID = 9;
private static final int I_ASK = 10;
private static final int I_CLOSE = 11;
//private static final int I_BID_SIZE = 12;
//private static final int I_ASK_SIZE = 13;
private static SnapshotConnector instance;
private String id;
private String name;
protected Map<String, FeedSubscription> symbolSubscriptions;
private ListenerList listeners = new ListenerList(ListenerList.IDENTITY);
protected TimeZone timeZone;
private SimpleDateFormat dateTimeParser;
private SimpleDateFormat dateParser;
private SimpleDateFormat timeParser;
private NumberFormat numberFormat;
protected Thread thread;
private boolean stopping = false;
private boolean subscriptionsChanged = false;
public SnapshotConnector() {
symbolSubscriptions = new HashMap<String, FeedSubscription>();
timeZone = TimeZone.getTimeZone("America/New_York");
dateTimeParser = new SimpleDateFormat("MM/dd/yyyy h:mma"); //$NON-NLS-1$
dateTimeParser.setTimeZone(timeZone);
dateParser = new SimpleDateFormat("MM/dd/yyyy"); //$NON-NLS-1$
dateParser.setTimeZone(timeZone);
timeParser = new SimpleDateFormat("h:mma"); //$NON-NLS-1$
timeParser.setTimeZone(timeZone);
numberFormat = NumberFormat.getInstance(Locale.US);
}
public synchronized static SnapshotConnector getInstance() {
if (instance == null) {
instance = new SnapshotConnector();
}
return instance;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, java.lang.String, java.lang.Object)
*/
@Override
public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException {
id = config.getAttribute("id");
name = config.getAttribute("name");
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IFeedConnector#getId()
*/
@Override
public String getId() {
return id;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IFeedConnector#getName()
*/
@Override
public String getName() {
return name;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IFeedConnector#subscribe(org.eclipsetrader.core.feed.IFeedIdentifier)
*/
@Override
public IFeedSubscription subscribe(IFeedIdentifier identifier) {
synchronized (symbolSubscriptions) {
IdentifierType identifierType = IdentifiersList.getInstance().getIdentifierFor(identifier);
FeedSubscription subscription = symbolSubscriptions.get(identifierType.getSymbol());
if (subscription == null) {
subscription = new FeedSubscription(this, identifierType);
PropertyChangeSupport propertyChangeSupport = (PropertyChangeSupport) identifier.getAdapter(PropertyChangeSupport.class);
if (propertyChangeSupport != null) {
propertyChangeSupport.addPropertyChangeListener(this);
}
symbolSubscriptions.put(identifierType.getSymbol(), subscription);
setSubscriptionsChanged(true);
}
subscription.incrementInstanceCount();
return subscription;
}
}
protected void disposeSubscription(FeedSubscription subscription) {
synchronized (symbolSubscriptions) {
if (subscription.decrementInstanceCount() <= 0) {
IdentifierType identifierType = subscription.getIdentifierType();
if (subscription.getIdentifier() != null) {
PropertyChangeSupport propertyChangeSupport = (PropertyChangeSupport) subscription.getIdentifier().getAdapter(PropertyChangeSupport.class);
if (propertyChangeSupport != null) {
propertyChangeSupport.removePropertyChangeListener(this);
}
}
symbolSubscriptions.remove(identifierType.getSymbol());
setSubscriptionsChanged(true);
}
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IFeedConnector#connect()
*/
@Override
public void connect() {
if (thread == null || !thread.isAlive()) {
stopping = false;
thread = new Thread(this, name + " - Data Reader");
thread.start();
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IFeedConnector#disconnect()
*/
@Override
public void disconnect() {
stopping = true;
if (thread != null) {
try {
synchronized (thread) {
thread.notify();
}
thread.join(30 * 1000);
} catch (InterruptedException e) {
Status status = new Status(IStatus.ERROR, YahooActivator.PLUGIN_ID, 0, "Error stopping thread", e);
YahooActivator.log(status);
}
thread = null;
}
}
public boolean isStopping() {
return stopping;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
try {
HttpClient client = new HttpClient();
client.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
Util.setupProxy(client, Util.snapshotFeedHost);
synchronized (thread) {
while (!isStopping()) {
synchronized (symbolSubscriptions) {
if (symbolSubscriptions.size() != 0) {
String[] symbols = symbolSubscriptions.keySet().toArray(new String[symbolSubscriptions.size()]);
fetchLatestSnapshot(client, symbols, false);
setSubscriptionsChanged(false);
}
}
try {
thread.wait(5000);
} catch (InterruptedException e) {
// Ignore exception, not important at this time
}
}
}
} catch (Exception e) {
Status status = new Status(IStatus.ERROR, YahooActivator.PLUGIN_ID, 0, "Error reading data", e);
YahooActivator.log(status);
}
}
protected void fetchLatestSnapshot(HttpClient client, String[] symbols, boolean isStaleUpdate) {
HttpMethod method = null;
BufferedReader in = null;
String line = ""; //$NON-NLS-1$
try {
method = Util.getSnapshotFeedMethod(symbols);
client.executeMethod(method);
in = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
while ((line = in.readLine()) != null) {
processSnapshotData(line, isStaleUpdate);
}
FeedSubscription[] subscriptions;
synchronized (symbolSubscriptions) {
Collection<FeedSubscription> c = symbolSubscriptions.values();
subscriptions = c.toArray(new FeedSubscription[c.size()]);
}
for (int i = 0; i < subscriptions.length; i++) {
subscriptions[i].fireNotification();
}
} catch (Exception e) {
Status status = new Status(IStatus.ERROR, YahooActivator.PLUGIN_ID, 0, "Error reading data", e);
YahooActivator.log(status);
} finally {
try {
if (in != null) {
in.close();
}
if (method != null) {
method.releaseConnection();
}
} catch (Exception e) {
Status status = new Status(IStatus.WARNING, YahooActivator.PLUGIN_ID, 0, "Connection wasn't closed cleanly", e);
YahooActivator.log(status);
}
}
}
void processSnapshotData(String line, boolean isStaleUpdate) {
String[] elements;
if (line.indexOf(";") != -1) {
elements = line.split(";"); //$NON-NLS-1$
}
else {
elements = line.split(","); //$NON-NLS-1$
}
String symbol = stripQuotes(elements[I_CODE]);
FeedSubscription subscription = symbolSubscriptions.get(symbol);
if (subscription != null) {
IdentifierType identifierType = subscription.getIdentifierType();
PriceDataType priceData = identifierType.getPriceData();
priceData.setTime(getDateValue(elements[I_DATE], elements[I_TIME]));
priceData.setLast(getDoubleValue(elements[I_LAST]));
priceData.setVolume(getLongValue(elements[I_VOLUME]));
subscription.setTrade(new Trade(priceData.getTime(), priceData.getLast(), null, priceData.getVolume()));
priceData.setBid(getDoubleValue(elements[I_BID]));
if (!isStaleUpdate) {
priceData.setBidSize(null); // getLongValue(elements[I_BID_SIZE]));
}
priceData.setAsk(getDoubleValue(elements[I_ASK]));
if (!isStaleUpdate) {
priceData.setAskSize(null); // getLongValue(elements[I_ASK_SIZE]));
}
subscription.setQuote(new Quote(priceData.getBid(), priceData.getAsk(), priceData.getBidSize(), priceData.getAskSize()));
priceData.setOpen(getDoubleValue(elements[I_OPEN]));
priceData.setHigh(getDoubleValue(elements[I_HIGH]));
priceData.setLow(getDoubleValue(elements[I_LOW]));
if (priceData.getOpen() != null && priceData.getOpen() != 0.0 && priceData.getHigh() != null && priceData.getHigh() != 0.0 && priceData.getLow() != null && priceData.getLow() != 0.0) {
subscription.setTodayOHL(new TodayOHL(priceData.getOpen(), priceData.getHigh(), priceData.getLow()));
}
priceData.setClose(getDoubleValue(elements[I_CLOSE]));
subscription.setLastClose(new LastClose(priceData.getClose(), null));
}
}
protected Date getDateValue(String dateValue, String timeValue) {
String date = stripQuotes(dateValue);
String time = stripQuotes(timeValue);
if (date.indexOf("N/A") != -1 && time.indexOf("N/A") != -1) {
return null;
}
try {
if (date.indexOf("N/A") != -1) {
date = dateParser.format(Calendar.getInstance(timeZone).getTime());
}
if (time.indexOf("N/A") != -1) {
time = timeParser.format(Calendar.getInstance(timeZone).getTime());
}
Calendar c = Calendar.getInstance();
c.setTime(dateTimeParser.parse(date + " " + time)); //$NON-NLS-1$
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
c.setTimeZone(TimeZone.getDefault());
if (c.get(Calendar.YEAR) < 70) {
c.add(Calendar.YEAR, 2000);
}
return c.getTime();
} catch (ParseException e) {
Status status = new Status(IStatus.ERROR, YahooActivator.PLUGIN_ID, 0, "Error parsing date/time values", e);
YahooActivator.log(status);
}
return null;
}
protected Double getDoubleValue(String value) {
try {
if (!value.equals("") && !value.equalsIgnoreCase("N/A")) {
return numberFormat.parse(value).doubleValue();
}
} catch (ParseException e) {
Status status = new Status(IStatus.ERROR, YahooActivator.PLUGIN_ID, 0, "Error parsing number", e);
YahooActivator.log(status);
}
return null;
}
protected Long getLongValue(String value) {
try {
if (!value.equals("") && !value.equalsIgnoreCase("N/A")) {
return numberFormat.parse(value).longValue();
}
} catch (ParseException e) {
Status status = new Status(IStatus.ERROR, YahooActivator.PLUGIN_ID, 0, "Error parsing number", e);
YahooActivator.log(status);
}
return null;
}
protected String stripQuotes(String s) {
if (s.startsWith("\"")) {
s = s.substring(1);
}
if (s.endsWith("\"")) {
s = s.substring(0, s.length() - 1);
}
return s;
}
protected boolean isSubscriptionsChanged() {
return subscriptionsChanged;
}
protected void setSubscriptionsChanged(boolean subscriptionsChanged) {
this.subscriptionsChanged = subscriptionsChanged;
}
Map<String, FeedSubscription> getSymbolSubscriptions() {
return symbolSubscriptions;
}
/* (non-Javadoc)
* @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
*/
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getSource() instanceof IFeedIdentifier) {
IFeedIdentifier identifier = (IFeedIdentifier) evt.getSource();
synchronized (symbolSubscriptions) {
for (FeedSubscription subscription : symbolSubscriptions.values()) {
if (subscription.getIdentifier() == identifier) {
symbolSubscriptions.remove(subscription.getIdentifierType().getSymbol());
IdentifierType identifierType = IdentifiersList.getInstance().getIdentifierFor(identifier);
subscription.setIdentifierType(identifierType);
symbolSubscriptions.put(identifierType.getSymbol(), subscription);
setSubscriptionsChanged(true);
break;
}
}
}
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IFeedConnector#addConnectorListener(org.eclipsetrader.core.feed.IConnectorListener)
*/
@Override
public void addConnectorListener(IConnectorListener listener) {
listeners.add(listener);
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IFeedConnector#removeConnectorListener(org.eclipsetrader.core.feed.IConnectorListener)
*/
@Override
public void removeConnectorListener(IConnectorListener listener) {
listeners.remove(listener);
}
}