
<!-- yahoo finance API to request financial info for stocks -->
<dependency>
<groupId>com.yahoofinance-api</groupId>
<artifactId>YahooFinanceAPI</artifactId>
<version>3.5.0</version>
</dependency>
YahooFinanceDataCollectorImpl, which wraps the Finance Quotes API specific code:package de.dlopes.stocks.facilitator.services.impl;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import yahoofinance.Stock;
import yahoofinance.YahooFinance;
import de.dlopes.stocks.facilitator.services.impl.util.SFApplicationException;
public class YahooFinanceDataCollectorImpl {
public static Map<String,Stock> requestStocks(List<String> symbols) throws SFApplicationException {
Map<String,Stock> result = new HashMap<String,Stock>();
String[] yhooSymbols = symbols.toArray(new String[symbols.size()]);
try {
Map<String,Stock> quotes = YahooFinance.get(yhooSymbols);
result = quotes;
} catch (IOException ioe) {
throw new SFApplicationException("Error during load of stock details for symbols from yahoo finance API", ioe);
}
return result;
}
}



package de.dlopes.stocks.facilitator.ui.forms;
import java.io.Serializable;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import lombok.Data;
import de.dlopes.stocks.facilitator.config.ConfigurationSettings;
import de.dlopes.stocks.facilitator.services.FinanceDataExtractor;
@Data
public class AddStocksForm implements Serializable {
private static final long serialVersionUID = 1764745848460626971L;
@Autowired
ConfigurationSettings config;
private String url;
private String listOfYahooSymbols;
public void validateInput(FacesContext fc, UIComponent component, Object object) {
FinanceDataExtractor finDataExtr = config.getFinanceDataExtractor();
if (!StringUtils.isEmpty(url) && !finDataExtr.isApplicable(url)) {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "URL not applicable", "URL '" + url + "' is not applicable for automatic processing.");
FacesContext.getCurrentInstance().addMessage("messages", msg);
} else if (StringUtils.isEmpty(listOfYahooSymbols)) {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "No information provided", "Neither URL nor Yahoo Symbols where provided.");
FacesContext.getCurrentInstance().addMessage("messages", msg);
}
}
}
stock-info-flow.xml:<var name="addStocksForm" class="de.dlopes.stocks.facilitator.ui.forms.AddStocksForm"/>
<view-state id="loadStockInfo">
<transition on="load" to="viewStockInfo">
<evaluate expression="stockInfoService.addStocks(addStocksForm)" />
</transition>
</view-state>
<view-state id="viewStockInfo">
<var name="stocks" class="de.dlopes.stocks.facilitator.ui.forms.LazyStockDataModel"/>
<transition on="select" to="showStockInfoDetails">
<set name="flowScope.stock" value="stocks.selected" />
</transition>
<transition on="end" to="finish" />
</view-state>
<view-state id="showStockInfoDetails">
<transition on="back" to="viewStockInfo" />
</view-state>
package de.dlopes.stocks.facilitator.ui.forms;
import java.util.List;
import java.util.Map;
import javax.persistence.Transient;
import org.primefaces.model.LazyDataModel;
import org.primefaces.model.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import de.dlopes.stocks.facilitator.data.StockInfo;
import de.dlopes.stocks.facilitator.data.StockInfoRepository;
import de.dlopes.stocks.facilitator.services.StockInfoService;
import de.dlopes.stocks.facilitator.services.impl.StockInfoServiceImpl;
public class LazyStockDataModel extends LazyDataModel<StockInfo> {
private static final long serialVersionUID = -8832831134966938627L;
// service needs to be declared as 'transient' in order to avoid Serialization Issue
// with org.springframework.dao.support.PersistenceExceptionTranslationInterceptor
private transient StockInfoService stockInfoService;
private List<StockInfo> stocks;
private StockInfo selected;
@Autowired
public void setStockInfoService(StockInfoService stockInfoService) {
this.stockInfoService = stockInfoService;
}
@Override
public List<StockInfo> load(int first, int pageSize, String sortField, SortOrder order, Map<String, Object> filters) {
// let's keep it easy for now: we neglect any pagination, sorting or filters
this.stocks = stockInfoService.findAll();
return stocks;
}
@Override
public StockInfo getRowData(String rowKey) {
for (StockInfo stock : this.stocks){
if (stock.getIsin().equals(rowKey)) {
return stock;
}
}
return null;
}
@Override
public Object getRowKey(StockInfo stock) {
return stock.getIsin();
}
@Override
public int getRowCount() {
return stockInfoService.count();
}
public StockInfo getSelected() {
return selected;
}
public void setSelected(StockInfo selected) {
this.selected = selected;
}
public int getCurrentPage() {
// let's keep it easy for now: there is only one page
return 1;
}
public int getPageSize() {
// let's keep it easy for now: page size = row count
return getRowCount();
}
}
<p:dataTable id="stocks" var="si" value="#{stocks}" paginator="true" dynamic="true" rows="#{stocks.pageSize}" page="#{stocks.currentPage}" lazy="true">
<f:facet name="header">Stocks</f:facet>
<p:column>
<f:facet name="header">Action</f:facet>
<p:commandButton id="viewStockDetails" value="View" action="select" icon="ui-icon-search">
<f:setPropertyActionListener value="#{si}" target="#{stocks.selected}" />
</p:commandButton>
</p:column>
</p:dataTable>

On click on the "View" button, all (currently only the ISIN and the last changed date are listed, but you get the idea):

This listing is rendered by the view showStockInfoDetails.xhtml:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!--/*
Copyright (c) 2016 Dominique Lopes.
All rights reserved.
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
Contributors:
Dominique Lopes - initial API and implementation
*/-->
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
template="/WEB-INF/layouts/standard.xhtml">
<ui:define name="title">Detail</ui:define>
<ui:define name="content">
<h:form>
<p:panel header="Stock: #{stock.name}">
<h:panelGrid columns="1">
<h:outputText value="ISIN: #{stock.isin}" />
<h:outputText value="Last Changed: #{stock.lastChanged}" />
<p:commandButton value="back" action="back" />
</h:panelGrid>
</p:panel>
</h:form>
</ui:define>
</ui:composition>
Today, we started to extend our application with some actual coding to dynamically load finance data and to show it in the UI. This setup allows us now to add more data in the next sessions until we are finally able to build a more complex analysis on top of that data.
As always, this blog post was inspired by some existing webpages - here they are:

Dominique Lopes is a Senior SAP Consultant at MHP with more than 10 years experience in Software Development in various programming languages. In his leisure time he enjoys to try out new IT trends in his private software projects.