All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ddfplus.service.feed.FeedServiceImpl Maven / Gradle / Ivy

There is a newer version: 1.1.7
Show newest version
/**
 * Copyright (C) 2004 - 2015 by Barchart.com, Inc. All Rights Reserved.
 * 
 * This software is the proprietary information of Barchart.com, Inc.
 * Use is subject to license terms.
 */
package com.ddfplus.service.feed;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.ddfplus.api.QuoteHandler;
import com.ddfplus.db.DataMaster;
import com.ddfplus.db.Quote;
import com.ddfplus.service.usersettings.UserSettings;
import com.ddfplus.util.XMLNode;
import com.ddfplus.util.XmlUtil;

/**
 * Feed Service Implementation
 *
 */
public class FeedServiceImpl implements FeedService {

	private static final int REQUEST_THREAD_INITIAL_DELAY_MS = 100;

	private static final long REQUEST_THREAD_TIMEOUT_MS = 500;

	private static final int REQUEST_MAX_SYMBOLS = 200;

	private final Logger log = LoggerFactory.getLogger(getClass());

	private final UserSettings userSettings;

	private final String baseUrl;

	private DataMaster datamaster;

	private BlockingQueue requestQ = new LinkedBlockingQueue(100);

	private ScheduledExecutorService es = Executors.newScheduledThreadPool(1);

	private RequestThread requestThread;

	private Map quoteExchangehandlers;

	// for testing only
	private String queryUrl;

	public FeedServiceImpl(DataMaster datamaster, UserSettings userSettings) {
		this(datamaster, userSettings, null);
	}

	public FeedServiceImpl(DataMaster datamaster, UserSettings userSettings,
			Map quoteExchangehandlers) {
		this.datamaster = datamaster;
		this.userSettings = userSettings;
		this.quoteExchangehandlers = quoteExchangehandlers;
		this.baseUrl = buildBaseUrl(this.userSettings);
		requestThread = new RequestThread(requestQ);
		es.scheduleAtFixedRate(requestThread, REQUEST_THREAD_INITIAL_DELAY_MS, REQUEST_THREAD_TIMEOUT_MS,
				TimeUnit.MILLISECONDS);
		log.info("Snapshot/Refresh is enabled for user: " + userSettings.getUserName());
	}

	@Override
	public void scheduleQuoteRefresh(String symbol) {
		FeedCmd cmd = new FeedCmd(CMDS.QuoteRefresh, symbol);
		requestQ.offer(cmd);
	}

	/*
	 * Queries web service for snapshot refresh, parses it, and updates the
	 * caches.
	 */
	void getQuotes(List symbolList) {
		// Get Quote Refresh(s)
		String query = buildQueryString(symbolList);
		if (log.isDebugEnabled()) {
			log.debug("Sending refresh request: " + query);
		}
		try {
			DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			Document doc = builder.parse(query);

			if (log.isDebugEnabled()) {
				log.debug(XmlUtil.printDocument(doc));
			}

			Element root = doc.getDocumentElement();
			NodeList list = root.getElementsByTagName("QUOTE");
			for (int i = 0; i < list.getLength(); i++) {
				try {
					Node node = list.item(i);
					XMLNode n = XMLNode.fromElement((Element)node);
					Quote q = Quote.fromXMLNode(n);

					// Update the Cache
					datamaster.putQuote(q);
					
					/*
					 * We have the snapshot now, callback the handler
					 */
					if (quoteExchangehandlers != null) {
						String ddfExchange = q.getDDFExchange();
						QuoteHandler eh = quoteExchangehandlers.get(ddfExchange);
						if (eh != null) {
							eh.onQuote(q);
						}
					}
				} catch (Exception e2) {
					log.error("Parsing Quote Error: " + e2.getMessage());
				}
			}

			// Not supported since DDF provides the whole book.
			list = root.getElementsByTagName("BOOK");

			// Not supported yet, do we need it?
			list = root.getElementsByTagName("CV");
			for (int i = 0; i < list.getLength(); i++) {
				try {
					Node node = list.item(i);
					processCV(node);
				} catch (Exception e2) {
					log.error("CV Parsing Error: " + e2.getMessage());
				}
			}

		} catch (ParserConfigurationException e) {
			log.error("Parser configuration on snapshot/refresh request: " + e.getMessage());
		} catch (SAXException e) {
			log.error("Could not parse snapshot/refresh request: " + e.getMessage());
		} catch (IOException e) {
			log.error("IO issue with snaphost/refresh request: " + e.getMessage());
		}
	}

	private void processCV(Node node) {

	}

	private void processBook(Node node) {

	}


	private String buildQueryString(List symbols) {
		// for testing only
		if (queryUrl != null) {
			return queryUrl;
		}
		StringBuilder sb = new StringBuilder(baseUrl);

		for (int i = 0; i < symbols.size(); i++) {
			try {
				String s = symbols.get(i);
				// kill whitespace
				s = s.replaceAll("\\s+", "");
				if (i == 0) {
					sb.append(URLEncoder.encode(s, "UTF-8"));
				} else {
					sb.append(",").append(URLEncoder.encode(s, "UTF-8"));
				}
			} catch (UnsupportedEncodingException e) {
				log.error("URL encode error: ", e);
			}
		}
		return sb.toString();
	}

	private String buildBaseUrl(UserSettings us) {
		String s = us.getStreamPrimaryServer();
		return "http://" + s + "/stream/quotes.jsx?" + "username=" + us.getUserName() + "&" + "password="
				+ us.getPassword() + "&symbols=";
	}

	private static class FeedCmd {
		private CMDS cmd;
		private String symbol;

		public FeedCmd(CMDS cmd, String symbol) {
			this.cmd = cmd;
			this.symbol = symbol;
		}

		public CMDS getCmd() {
			return cmd;
		}

		public void setCmd(CMDS cmd) {
			this.cmd = cmd;
		}

		public String getSymbol() {
			return symbol;
		}

		public void setSymbol(String symbols) {
			this.symbol = symbols;
		}
	}

	// for testing
	void setQueryUrl(String url) {
		this.queryUrl = url;
	}

	private class RequestThread implements Runnable {

		private BlockingQueue cmdQ;
		private List symbols = new ArrayList();

		public RequestThread(BlockingQueue q) {
			cmdQ = requestQ;
		}

		@Override
		public void run() {
			symbols.clear();
			while (cmdQ.peek() != null) {
				try {
					FeedCmd cmd = cmdQ.poll();
					if (cmd != null) {
						switch (cmd.getCmd()) {
						case QuoteRefresh:
							if (symbols.size() < REQUEST_MAX_SYMBOLS) {
								symbols.add(cmd.getSymbol());
							} else {
								// Send it
								getQuotes(symbols);
								symbols.clear();
							}
							break;
						default:
						}
					}
				} catch (Exception e) {
					log.error("Feed request error: " + e.getMessage());
				}
			}
			// Q is empty now, send any remaining requests
			if (symbols.size() > 0) {
				getQuotes(symbols);
			}
		}
	}

	private enum CMDS {
		QuoteRefresh;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy