/*
* Copyright (c) 2004-2006 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.archipelago;
import java.beans.PropertyChangeSupport;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.net.proxy.IProxyData;
import org.eclipse.core.net.proxy.IProxyService;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.IExecutableExtensionFactory;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Status;
import org.eclipsetrader.core.feed.BookEntry;
import org.eclipsetrader.core.feed.IBook;
import org.eclipsetrader.core.feed.IBookEntry;
import org.eclipsetrader.core.feed.IConnectorListener;
import org.eclipsetrader.core.feed.IFeedConnector2;
import org.eclipsetrader.core.feed.IFeedIdentifier;
import org.eclipsetrader.core.feed.IFeedSubscription;
import org.eclipsetrader.core.feed.IFeedSubscription2;
import org.eclipsetrader.core.feed.QuoteDelta;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
public class Level2Feed implements IFeedConnector2, Runnable, IExecutableExtension, IExecutableExtensionFactory {
private static Level2Feed instance;
private String id;
private String name;
private ListenerList listeners = new ListenerList(ListenerList.IDENTITY);
private Map<String, FeedSubscription> symbolSubscriptions;
private boolean subscriptionsChanged;
private Thread thread;
private boolean stopping;
private Socket socket;
private BufferedOutputStream os;
private BufferedReader is;
private Log logger = LogFactory.getLog(getClass());
public Level2Feed() {
if (instance == null) {
symbolSubscriptions = new HashMap<String, FeedSubscription>();
instance = this;
}
}
/* (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.eclipse.core.runtime.IExecutableExtensionFactory#create()
*/
@Override
public Object create() throws CoreException {
return instance;
}
/* (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) {
return null;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IFeedConnector2#subscribeLevel2(org.eclipsetrader.core.feed.IFeedIdentifier)
*/
@Override
public IFeedSubscription2 subscribeLevel2(IFeedIdentifier identifier) {
synchronized (symbolSubscriptions) {
FeedSubscription subscription = symbolSubscriptions.get(identifier.getSymbol());
if (subscription == null) {
subscription = new FeedSubscription(this, identifier);
PropertyChangeSupport propertyChangeSupport = (PropertyChangeSupport) identifier.getAdapter(PropertyChangeSupport.class);
if (propertyChangeSupport != null) {
; // TODO propertyChangeSupport.addPropertyChangeListener(this);
}
symbolSubscriptions.put(identifier.getSymbol(), subscription);
subscriptionsChanged = true;
}
if (subscription.getIdentifier() == null) {
subscription.setIdentifier(identifier);
PropertyChangeSupport propertyChangeSupport = (PropertyChangeSupport) identifier.getAdapter(PropertyChangeSupport.class);
if (propertyChangeSupport != null) {
; // TODO propertyChangeSupport.addPropertyChangeListener(this);
}
}
if (subscription.incrementInstanceCount() == 1) {
subscriptionsChanged = true;
}
return subscription;
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IFeedConnector2#subscribeLevel2(java.lang.String)
*/
@Override
public IFeedSubscription2 subscribeLevel2(String symbol) {
synchronized (symbolSubscriptions) {
FeedSubscription subscription = symbolSubscriptions.get(symbol);
if (subscription == null) {
subscription = new FeedSubscription(this, symbol);
symbolSubscriptions.put(symbol, subscription);
subscriptionsChanged = true;
}
if (subscription.incrementInstanceCount() == 1) {
subscriptionsChanged = true;
}
return subscription;
}
}
public void disposeSubscription(FeedSubscription subscription) {
synchronized (symbolSubscriptions) {
if (subscription.decrementInstanceCount() <= 0) {
if (subscription.getIdentifier() != null) {
PropertyChangeSupport propertyChangeSupport = (PropertyChangeSupport) subscription.getIdentifier().getAdapter(PropertyChangeSupport.class);
if (propertyChangeSupport != null) {
; // TODO propertyChangeSupport.removePropertyChangeListener(this);
}
}
symbolSubscriptions.remove(subscription.getSymbol());
subscriptionsChanged = true;
}
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IFeedConnector#connect()
*/
@Override
public void connect() {
if (thread == null) {
stopping = false;
thread = new Thread(this);
thread.start();
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IFeedConnector#disconnect()
*/
@Override
public void disconnect() {
stopping = true;
if (thread != null) {
try {
thread.join(30 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread = null;
}
}
/* (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);
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
String HOST = "datasvr.tradearca.com";
Set<String> sTit = new HashSet<String>();
for (int i = 0; i < 5 && !stopping; i++) {
try {
Proxy socksProxy = Proxy.NO_PROXY;
if (ArchipelagoPlugin.getDefault() != null) {
BundleContext context = ArchipelagoPlugin.getDefault().getBundle().getBundleContext();
ServiceReference reference = context.getServiceReference(IProxyService.class.getName());
if (reference != null) {
IProxyService proxy = (IProxyService) context.getService(reference);
IProxyData data = proxy.getProxyDataForHost(HOST, IProxyData.SOCKS_PROXY_TYPE);
if (data != null) {
if (data.getHost() != null) {
socksProxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(data.getHost(), data.getPort()));
}
}
context.ungetService(reference);
}
}
socket = new Socket(socksProxy);
socket.connect(new InetSocketAddress(HOST, 80));
os = new BufferedOutputStream(socket.getOutputStream());
is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
os.write("GET http://datasvr.tradearca.com/zrepeaterz/ HTTP/1.1\r\n\r\n".getBytes());
os.write("LogonRequest=DISABLED\r\n".getBytes());
os.flush();
break;
} catch (Exception e) {
Status status = new Status(IStatus.ERROR, ArchipelagoPlugin.PLUGIN_ID, 0, "Error connecting to server", e);
ArchipelagoPlugin.log(status);
try {
if (socket != null) {
socket.close();
}
} catch (Exception e1) {
}
socket = null;
is = null;
os = null;
}
}
if (socket == null || os == null || is == null) {
thread = null;
return;
}
while (!stopping) {
try {
if (subscriptionsChanged) {
Set<String> toAdd = new HashSet<String>();
Set<String> toRemove = new HashSet<String>();
synchronized (symbolSubscriptions) {
for (String s : symbolSubscriptions.keySet()) {
if (!sTit.contains(s)) {
toAdd.add(s);
}
}
for (String s : sTit) {
if (!symbolSubscriptions.containsKey(s)) {
toRemove.add(s);
}
}
subscriptionsChanged = false;
}
if (toRemove.size() != 0) {
for (String s : toRemove) {
os.write("MsgType=UnregisterBook&Symbol=".getBytes());
os.write(s.getBytes());
os.write("\r\n".getBytes());
}
logger.info("Removing " + toRemove);
os.flush();
}
if (toAdd.size() != 0) {
for (String s : toAdd) {
os.write("MsgType=RegisterBook&Symbol=".getBytes());
os.write(s.getBytes());
os.write("\r\n".getBytes());
}
logger.info("Adding " + toAdd);
os.flush();
}
sTit.removeAll(toRemove);
sTit.addAll(toAdd);
}
if (!is.ready()) {
try {
Thread.sleep(100);
} catch (Exception e) {
}
continue;
}
String inputLine = is.readLine();
if (inputLine.startsWith("BK&")) {
String[] sections = inputLine.split("&");
if (sections.length < 4) {
continue;
}
String symbol = sections[1];
int index = 0, item = 0;
String[] elements = sections[2].split("#");
List<BookEntry> bid = new ArrayList<BookEntry>();
while (index < elements.length) {
Double price = new Double(elements[index++]);
Long quantity = new Long(elements[index++]);
index++; // Time
String id = elements[index++];
bid.add(new BookEntry(null, price, quantity, 1L, id));
item++;
}
index = 0;
item = 0;
elements = sections[3].split("#");
List<BookEntry> ask = new ArrayList<BookEntry>();
while (index < elements.length) {
Double price = new Double(elements[index++]);
Long quantity = new Long(elements[index++]);
index++; // Time
String id = elements[index++];
ask.add(new BookEntry(null, price, quantity, 1L, id));
item++;
}
FeedSubscription subscription = symbolSubscriptions.get(symbol);
if (subscription != null) {
IBook oldValue = subscription.getBook();
IBook newValue = new org.eclipsetrader.core.feed.Book(bid.toArray(new IBookEntry[bid.size()]), ask.toArray(new IBookEntry[ask.size()]));
subscription.setBook(newValue);
subscription.addDelta(new QuoteDelta(subscription.getIdentifier(), oldValue, newValue));
subscription.fireNotification();
}
}
} catch (SocketException e) {
for (int i = 0; i < 5 && !stopping; i++) {
try {
socket = new Socket("datasvr.tradearca.com", 80);
is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
os = new BufferedOutputStream(socket.getOutputStream());
os.write("GET http://datasvr.tradearca.com/zrepeaterz/ HTTP/1.1\r\n\r\n".getBytes());
os.write("LogonRequest=DISABLED\r\n".getBytes());
os.flush();
for (String s : sTit) {
os.write("MsgType=RegisterBook&Symbol=".getBytes());
os.write(s.getBytes());
os.write("\r\n".getBytes());
}
os.flush();
break;
} catch (Exception e1) {
Status status = new Status(IStatus.ERROR, ArchipelagoPlugin.PLUGIN_ID, 0, "Error connecting to server", e);
ArchipelagoPlugin.log(status);
}
}
if (socket == null || os == null || is == null) {
thread = null;
return;
}
} catch (Exception e) {
Status status = new Status(IStatus.ERROR, ArchipelagoPlugin.PLUGIN_ID, 0, "Error receiving stream", e);
ArchipelagoPlugin.log(status);
break;
}
}
try {
if (socket != null) {
socket.close();
}
socket = null;
os = null;
is = null;
} catch (Exception e) {
// Do nothing
}
thread = null;
}
}