StocksRepositoryImpl.java

/**
 * Copyright 2012 the original author or authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.phisix.api.repository;

import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.ws.rs.NotFoundException;
import javax.ws.rs.client.Client;

import org.apache.commons.lang3.time.DatePrinter;
import org.apache.commons.lang3.time.FastDateFormat;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
import org.jboss.resteasy.client.jaxrs.engines.URLConnectionEngine;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.datastore.TransactionOptions;
import com.google.appengine.api.memcache.Expiration;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.googlecode.phisix.api.client.GaClient;
import com.googlecode.phisix.api.client.GaClientConstants;
import com.googlecode.phisix.api.client.PseClient;
import com.googlecode.phisix.api.client.PseClientConstants;
import com.googlecode.phisix.api.client.PseFramesClient;
import com.googlecode.phisix.api.model.Price;
import com.googlecode.phisix.api.model.Stock;
import com.googlecode.phisix.api.model.Stocks;

/**
 * 
 * @author Edge Dalmacio
 *
 */
public class StocksRepositoryImpl implements StocksRepository {

	private static final DatePrinter datePrinter = FastDateFormat.getInstance("yyyy-MM-dd", TimeZone.getTimeZone("GMT+8"));
	private final PseClient pseClient;
//	private final GaClient gaClient;
	private final Pattern pattern = Pattern.compile("\"listedCompany_companyId\":\"(\\d+)\".*\"securityId\":\"(\\d+)\"");
	private final DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
	private final MemcacheService memcache = MemcacheServiceFactory.getMemcacheService();
	
	public StocksRepositoryImpl() {
		this.pseClient = new PseFramesClient();
		
		// TODO make this async
		Client gaClient = new ResteasyClientBuilder()
				.httpEngine(new URLConnectionEngine())
				.build();
//		this.gaClient = ((ResteasyWebTarget) gaClient.target("http://www.google-analytics.com")).proxy(GaClient.class);
	}
	
	public StocksRepositoryImpl(PseClient client) {
		this(client, null);
	}

	public StocksRepositoryImpl(PseClient pseClient, GaClient gaClient) {
		this.pseClient = pseClient;
//		this.gaClient = gaClient;
	}
	
	@Override
	public Stocks findAll() {
		Stocks stocks = (Stocks) memcache.get("ALL");
		if (stocks == null) {
			stocks = pseClient.getSecuritiesAndIndicesForPublic(PseClientConstants.REFERER, "getSecuritiesAndIndicesForPublic", true);
			memcache.put("ALL", stocks, Expiration.byDeltaSeconds(60));
		}
//		if (gaClient != null) {
//			gaClient.eventTracking(GaClientConstants.VERSION, GaClientConstants.TRACKING_ID, GaClientConstants.CLIENT_ID, GaClientConstants.EVENT_HIT, "stocks", "all", GaClientConstants.USER_AGENT);
//		}
		return stocks;
	}
	
	@Override
	public String findBySymbol(String symbol) {
		
		String securityOrCompany = pseClient.findSecurityOrCompany(
				"findSecurityOrCompany", true,
				String.format("start=0&limit=1&query=%s", symbol));
		
		Matcher matcher = pattern.matcher(securityOrCompany);
		
		if (matcher.find()) {
//			if (gaClient != null) {
//				gaClient.eventTracking(GaClientConstants.VERSION, GaClientConstants.TRACKING_ID, GaClientConstants.CLIENT_ID, GaClientConstants.EVENT_HIT, "stocks", symbol, GaClientConstants.USER_AGENT);
//			}
			return pseClient.companyInfo("fetchHeaderData", true, 
					String.format("company=%s&security=%s", 
							matcher.group(1),
							matcher.group(2)));
		}
		
		return null;
	}
	
	@Override
	public Stocks findBySymbolAndTradingDate(String symbol, Date tradingDate) {
		Key stockKey = KeyFactory.createKey("Stock", symbol);
		Entity stockEntity;
		try {
			stockEntity = datastore.get(stockKey);
		} catch (EntityNotFoundException e) {
			throw new NotFoundException();
		}

		Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT+8"));
		calendar.setTime(tradingDate);
		
		Key historyKey = KeyFactory.createKey(stockKey, "History", calendar.getTimeInMillis());
		Entity historyEntity;
		try {
			historyEntity = datastore.get(historyKey);
		} catch (EntityNotFoundException e) {
			throw new NotFoundException();
		}
		
		Price price = new Price();
		price.setCurrency("PHP");
		price.setAmount(new BigDecimal((String) historyEntity.getProperty("close")));

		Stock stock = new Stock();
		stock.setSymbol(symbol);
		stock.setName((String) stockEntity.getProperty("name"));
		stock.setPrice(price);
		stock.setVolume((Long) historyEntity.getProperty("volume"));
		
		Stocks stocks = new Stocks();
		stocks.setAsOf(calendar);
		stocks.getStocks().add(stock);
		
//		if (gaClient != null) {
//			String formattedTradingDate = datePrinter.format(tradingDate);
//			gaClient.eventTracking(GaClientConstants.VERSION, GaClientConstants.TRACKING_ID, GaClientConstants.CLIENT_ID, GaClientConstants.EVENT_HIT, "stocks", symbol + "." + formattedTradingDate, GaClientConstants.USER_AGENT);
//		}
		return stocks;
	}
	
	@Override
	public void save(Stocks stocks) {
		Calendar tradingDate = retrieveTradingDate(stocks);
		for (Stock stock : stocks.getStocks()) {
			Transaction txn = datastore.beginTransaction(TransactionOptions.Builder.withXG(true));
			try {
				Entity stockEntity;
				Key key = KeyFactory.createKey("Stock", stock.getSymbol());
				try {
					stockEntity = datastore.get(key);
				} catch (EntityNotFoundException e) {
					stockEntity = new Entity(key);
					stockEntity.setUnindexedProperty("name", stock.getName());
					datastore.put(stockEntity);
				}
				Entity historyEntity = new Entity("History", tradingDate.getTimeInMillis(), key);
				historyEntity.setProperty("tradingDate", tradingDate.getTime());
				historyEntity.setUnindexedProperty("close", stock.getPrice().getAmount().toPlainString());
				historyEntity.setUnindexedProperty("volume", stock.getVolume());
				datastore.put(historyEntity);
				txn.commit();
			} finally {
				if (txn.isActive()) {
					txn.rollback();
				}
			}
		}
	}

	private Calendar retrieveTradingDate(Stocks stocks) {
		Calendar tradingDate = stocks.getAsOf();
		tradingDate.set(Calendar.HOUR_OF_DAY, 0);
		tradingDate.set(Calendar.MINUTE, 0);
		tradingDate.set(Calendar.SECOND, 0);
		return tradingDate;
	}
}