package io.pivotal.quotes.service;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import io.pivotal.quotes.domain.*;
import io.pivotal.quotes.exception.SymbolNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.*;
import java.util.stream.Collectors;
/**
* A service to retrieve Company and Quote information.
*
* @author David Ferreira Pinto
*
*/
@Service
@RefreshScope
public class QuoteService {
@Value("${pivotal.quotes.quotes_url}")
protected String quote_url;
@Value("${pivotal.quotes.companies_url}")
protected String company_url;
@Value("${pivotal.quotes.yahoo_rest_query}")
protected String yahoo_url = "https://query.yahooapis.com/v1/public/yql?q=select * from yahoo.finance.quotes where symbol in ('{symbol}')&format={fmt}&env={env}";
@Value("${pivotal.quotes.yahoo_env}")
protected String ENV = "http://datatables.org/alltables.env";
public static final String FMT = "json";
private static final Logger logger = LoggerFactory.getLogger(QuoteService.class);
/*
* cannot autowire as don't want ribbon here.
*/
private RestTemplate restTemplate;
public QuoteService() {
restTemplate = new RestTemplate();
ObjectMapper objectMapper = new ObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
messageConverter.setObjectMapper(objectMapper);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(messageConverter);
restTemplate.setMessageConverters(messageConverters);
}
/**
* Retrieve one or more quotes.
*
* @param symbols comma delimited list of symbols.
* @return a list of quotes.
*/
@HystrixCommand(fallbackMethod = "getMarkitondemandQuotes",
commandProperties = {@HystrixProperty(name="execution.timeout.enabled", value="false")})
public List<Quote> getQuotes(String symbols) throws SymbolNotFoundException {
logger.debug("retrieving quotes for: " + symbols);
if ( symbols.isEmpty() ) return new ArrayList<>();
YahooQuoteResponses responses = restTemplate.getForObject(yahoo_url, YahooQuoteResponses.class, symbols, FMT, ENV);
logger.debug("Got responses: " + responses);
List<YahooQuote> yahooQuotes = responses.getResults().getQuoteList().getQuote();
Date createDate = responses.getResults().getCreated();
List<Quote> quotes = yahooQuotes
.stream()
.map(yQuote -> QuoteMapper.INSTANCE.mapFromYahooQuote(yQuote, createDate))
.collect(Collectors.toList());
for (Quote quote : quotes) {
if ( quote.getName() == null ) throw new SymbolNotFoundException( quote.getSymbol() + " not found" );
}
return quotes;
}
/**
* Retrieves an up to date quote for the given symbol.
*
* @param symbols Array of symbols to retrieve quotes for.
* @return The quote object or null if not found.
* @throws SymbolNotFoundException
*/
@HystrixCommand(fallbackMethod = "getQuotesFallback",
commandProperties = {@HystrixProperty(name="execution.timeout.enabled", value="false")})
@SuppressWarnings("unused")
public List<Quote> getMarkitondemandQuotes(String symbols) throws SymbolNotFoundException {
List<Quote> result = new ArrayList<>();
String[] splitSymbols = symbols.split(",");
for (String symbol : splitSymbols) {
logger.debug("QuoteService.getQuote: retrieving quote for: " + symbol);
Map<String, String> params = new HashMap<>();
params.put("symbol", symbol);
Quote quote = restTemplate.getForObject(quote_url, Quote.class, params);
logger.debug("QuoteService.getQuote: retrieved quote: " + quote);
result.add(quote);
if (quote.getSymbol() == null) {
throw new SymbolNotFoundException("Symbol not found: " + symbol);
}
}
return result;
}
@SuppressWarnings("unused")
private List<Quote> getQuotesFallback(String symbols) {
logger.debug("QuoteService.getQuoteFallback: circuit opened for symbols: " + symbols);
throw new RuntimeException("Quote service unavailable.");
}
/**
* Retrieves a list of CompanyInfo objects. Given the name parameters, the
* return list will contain objects that match the search both on company
* name as well as symbol.
*
* @param name
* The search parameter for company name or symbol.
* @return The list of company information.
*/
@HystrixCommand(fallbackMethod = "getCompanyInfoFallback",
commandProperties = {@HystrixProperty(name="execution.timeout.enabled", value="false")})
public List<CompanyInfo> getCompanyInfo(String name) {
logger.debug("QuoteService.getCompanyInfo: retrieving info for: " + name);
Map<String, String> params = new HashMap<>();
params.put("name", name);
CompanyInfo[] companies = restTemplate.getForObject(company_url,
CompanyInfo[].class, params);
logger.debug("QuoteService.getCompanyInfo: retrieved info: " + Arrays.toString(companies));
return Arrays.asList(companies);
}
@SuppressWarnings("unused")
private Quote getCompanyInfoFallback(String symbol)
throws SymbolNotFoundException {
logger.debug("QuoteService.getCompanyInfoFallback: circuit opened for symbol: "
+ symbol);
throw new RuntimeException("Company Info service unavailable.");
}
}