package mobi.boilr.libdynticker.core;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.sql.Timestamp;
import java.util.List;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import mobi.boilr.libdynticker.core.exception.NoMarketDataException;
/**
* Abstract template for Exchange
*/
public abstract class Exchange {
private long expiredPeriod;
private List<Pair> pairs;
private Timestamp timestamp = null;
private String name;
private static final int CONN_TIMEOUT = 8000;
private ObjectMapper mapper = new ObjectMapper();
public Exchange(String name, long expiredPeriod) {
setExpiredPeriod(expiredPeriod);
this.name = name;
}
/**
* @param pair of exchange/coin
*
* @return Returns the last value of the exchange for a given pair
* coin/exchange. We have to use a double because some exchanges measure
* values in satoshis (10^-8). A float has just 24 bits of precision which
* is not enough to represent 8 decimal places.
* @throws NumberFormatException
* @throws IOException
* @throws NoMarketDataException
*/
public double getLastValue(Pair pair) throws NumberFormatException, IOException, NoMarketDataException {
double lastValue = Double.parseDouble(getTicker(pair));
return lastValue;
}
final public List<Pair> getPairs() throws IOException {
long currentTime = System.currentTimeMillis();
if(timestamp == null) {
pairs = getPairsFromAPI();
timestamp = new Timestamp(currentTime);
return pairs;
}
else if((currentTime - getTimestamp().getTime()) < getExpiredPeriod()) {
return pairs;
}
else {
/*
* TODO throw a custom exception where there is no internet
* connection. The exception includes the previous list of pairs and
* the timestamp.
*/
return pairs = getPairsFromAPI();
}
}
/**
* Get pairs from a remote API, specific for each exchange
*
* @return List of traded pairs
* @throws IOException
*/
protected abstract List<Pair> getPairsFromAPI() throws IOException;
/**
*
* @param pair
* @return Json with ticker information
* @throws IOException
* @throws NoMarketDataException
*/
protected abstract String getTicker(Pair pair) throws IOException, NoMarketDataException;
protected JsonNode readJsonFromUrl(String url) throws IOException {
URLConnection urlConnection = buildConnection(url);
urlConnection.connect();
return mapper.readTree(urlConnection.getInputStream());
}
protected JsonNode readJsonFromUrl(String url, String postData) throws IOException {
HttpURLConnection conn = (HttpURLConnection) buildConnection(url);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", "" + Integer.toString(postData.getBytes().length));
conn.setUseCaches(false);
DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
wr.writeBytes(postData);
wr.flush();
wr.close();
return mapper.readTree(conn.getInputStream());
}
protected URLConnection buildConnection(String url) throws IOException, MalformedURLException {
URLConnection urlConnection = (new URL(url)).openConnection();
/*
* Some exchanges return an HTTP 403 error (Forbidden) when you try to
* access the API with an undefined User-Agent.
*/
urlConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11");
// Others return HTTP 406 (Not Acceptable) when Accept is undefined.
urlConnection.addRequestProperty("Accept", "*/*");
urlConnection.setConnectTimeout(CONN_TIMEOUT);
urlConnection.setReadTimeout(CONN_TIMEOUT);
return urlConnection;
}
public abstract String parseTicker(JsonNode node, Pair pair) throws IOException, NoMarketDataException;
protected Timestamp getTimestamp() {
return timestamp;
}
public long getExpiredPeriod() {
return expiredPeriod;
}
public void setExpiredPeriod(long expiredPeriod) {
this.expiredPeriod = expiredPeriod;
}
public String getName() {
return name;
}
}