Creando un KP para el contenedor de KPs de Sofia2

En el siguiente post vamos a mostrar cómo crear una App Sofia2 (KP) para desplegarla en el contenedor de KPs de Sofia2.

En concreto vamos a crear una App que insertará en la plataforma información sobre las cotizaciones en bolsa de las 30 empresas que componen el índice Dow Jones. Se tratará de un KP Java, que utilizando el API de Yahoo, consultará la última cotización de cada valor y creará un fichero de mensajes interpretable por el contenedor de KPs que será quien los transforme en mensajes SSAP INSERT y los envíe al SIB. Asimismo, configuraremos el KP en el contenedor para que sea invocado periódicamente y de este modo tengamos datos de cotización actualizados.

Para ello repasaremos el flujo de trabajo típico en Sofia2, y lo extenderemos con el proceso de programación y despliegue de un KP en el contenedor de KPs.

Desarrollo:

1. Creamos una ontologia para almacenar la información: En el caso de nuestra aplicación, gestionaremos información sobre la cotización en bolsa de las empresas del índice Dow Jones devuelta por el servicio de información de Yahoo Finance. Para ello, vamos a utilizar un API web provisto por Yahoo (https://developer.yahoo.com/yql/), que nos permite lanzar queries en formato similar a SQL sobre mas de 1000 tablas de sus sistemas, incluyendo la que a nosotros nos interesa que es yahoo.finance.quote. y pudiendo recibir la respuesta tanto en formato XML como en JSON.

Para determinar que debe tener nuestra ontologia, hacemos previamente una consulta al servicio, solicitando la cotización de la empresa 3M, para la que recibimos la siguiente respuesta:

“symbol”: “MMM”,

“AverageDailyVolume”: “2925250”,

“Change”: “+0.18”,

“DaysLow”: “164.36”,

“DaysHigh”: “166.09”,

“YearLow”: “123.61”,

“YearHigh”: “166.09”,

“MarketCapitalization”: “106.0B”,

“LastTradePriceOnly”: “165.48”,

“DaysRange”: “164.36 – 166.09”,

“Name”: “3M Company Common”,

“Symbol”: “MMM”,

“Volume”: “4891344”,

“StockExchange”: “NYSE”

De esta información no quedamos con la siguiente para nuestra ontologia:

  • Symbol: Identificador de la empresa en Bolsa
  • Name: Nombre de la empresa
  • Volume: Número de acciones intercambiadas
  • StockExchange: Mercado en el que cotiza la empresa
  • Change: Variación porcentual del valor de la empresa desde el precio de cierre del dia anterior.
  • DaysLow: Precio de cotización mínima de la empresa desde el comiento del dia.
  • DaysHigh: Precio de cotización máxima de la empresa desde el comienzo del dia.
  • LastTradePriceOnly: Ultimo precio de cotización.

A la que sumaremos un timestamp con el momento en el que se ha recuperado la información.

Con todo esto determinamos que nuestra ontologia llamada StockPrice debe ser la siguiente:

{

“$schema”: “http://json-schema.org/draft-04/schema#“,

“title”: “StockPrice Schema”,

“type”: “object”,

“required”: [“StockPrice”],

“properties”: {

“StockPrice”: {

“type”: “string”,

“$ref”: “#/datos”

}

},

“datos”: {

“description”: “StockPrice”,

“type”: “object”,

“required”: [

“timestamp”,

“symbol”,

“name”,

“volume”,

“stockExchange”,

“change”,

“daysLow”,

“daysHigh”,

“lastTradePriceOnly”

],

“properties”: {

“timestamp”: {

“type”: “object”,

“required”: [

“$date”

],

“properties”: {

“$date”: {

“type”: “string”,

“format”: “date-time”

}

},

“additionalProperties”: false

},

“symbol”: {

“type”: “string”

},

“name”: {

“type”: “string”

},

“volume”: {

“type”: “string”

},

“stockExchange”: {

“type”: “string”

},

“change”: {

“type”: “string”

},

“daysLow”: {

“type”: “string”

},

“daysHigh”: {

“type”: “string”

},

“lastTradePriceOnly”: {

“type”: “string”

}

}

}

}

Y procedemos a crearla a partir de una plantilla vacía

2. Damos de alta el KP en Sofia2: Llamaremos a este KP DowJonesStockFeed

3. Programamos el KP Java que desplegaremos en el contenedor:

Como hemos dicho, utilizaremos un servicio externo, a través del API YQL de Yahoo para recibir la información de cotización de las empresas del índice Dow Jones. A este servicio externo le tendremos que pasar por cada empresa que queramos recuperar, un código o símbolo que la representa. Para ello, hemos creado una enumeración, que contiene un elemento por cada empresa, junto con su código:


package com.indra.sofia2.demos.stock.util;

/**
 * Enumeracion con las empresas del indice DowJones identificadas por su ticker o simbolo
 *
 */
public enum DowJonesStock {
 THREEM("MMM"), AMERICANEXPRESS("AXP"), ATANDT("T"), BOEING("BA"),
 CATERPILLAR("CAT"), CHEVRON("CVX"), CISCO("CSCO"), COCACOLA("KO"),
 DISNEY("DIS"), DUPONT("DD"), EXXON("XOM"), GENERALELECTRIC("GE"),
 GOLDMANSACHS("GS"), HOMEDEPOT("HD"), IBM("IBM"), INTEL("INTC"),
 JOHNSONANDJOHNSON("JNJ"), JPMORGAN("JPM"), MCDONALDS("MCD"), MERCK("MRK"),
 MICROSOFT("MSFT"), NIKE("NKE"), PFIZER("PFE"), PROCTERANDGAMBLE("PG"),
 TRAVELERS("TRV"), UNITEDTECH("UTX"), UNITEDHEALTH("UNH"), VERIZON("VZ"),
 VISA("V"), WALMART("WMT");
 
 //Este valor es lo que el API de Yahoo utiliza para localizar una empresa
 private String symbol;

 DowJonesStock(String symbol) {
       this.symbol = symbol;
 }
 
 public String getSymbol() {
      return symbol;
 }
}

A continuación creamos un Bean que nos ayudará a mapear cada cotización de una empresa recibida en la respuesta de la llamada al servicio externo, a una instancia de ontologia válida para Sofia2.

Este Bean tendrá todas las propiedades definidas en nuestra ontologia, junto con un método que nos devolverá el objeto como una instancia de ontologia lista para ser almacenada en un fichero de mensajes, que el contenedor de KPs podrá interpertar para construir un mensaje SSAP Insert:


package com.indra.sofia2.demos.stock.util;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Bean que respresenta cada una de las cotizaciones de un simbolo
 * Se utiliza para mapear desde una cotización recogida desde el API de Yahoo hacia una instancia de una ontologia
 *
 */
public class StockBean {
 
 private long timestamp;
 private String symbol;
 private String name;
 private String volume;
 private String stockExchange;
 private String change;
 private String daysLow;
 private String daysHigh;
 private String lastTradePriceOnly;
 
 
 public long getTimestamp() {
       return timestamp;
 }
 public void setTimestamp(long timestamp) {
       this.timestamp = timestamp;
 }
 public String getSymbol() {
       return symbol;
 }
 public void setSymbol(String symbol) {
       this.symbol = symbol;
 }
 public String getName() {
       return name;
 }
 public void setName(String name) {
      this.name = name;
 }
 public String getVolume() {
      return volume;
 }
 public void setVolume(String volume) {
      this.volume = volume;
 }
 public String getStockExchange() {
      return stockExchange;
 }
 public void setStockExchange(String stockExchange) {
      this.stockExchange = stockExchange;
 }
 public String getChange() {
      return change;
 }
 public void setChange(String change) {
      this.change = change;
 }
 public String getDaysLow() {
       return daysLow;
 }
 public void setDaysLow(String daysLow) {
       this.daysLow = daysLow;
 }
 public String getDaysHigh() {
       return daysHigh;
 }
 public void setDaysHigh(String daysHigh) {
       this.daysHigh = daysHigh;
 }
 public String getLastTradePriceOnly() {
      return lastTradePriceOnly;
 }
 public void setLastTradePriceOnly(String lastTradePriceOnly) {
      this.lastTradePriceOnly = lastTradePriceOnly;
 }
 
 public String getAsOntologyInstance(String ontology){
     SimpleDateFormat timestampFormat=new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
 
     StringBuilder ontologyInstance=new StringBuilder();
     ontologyInstance.append("{\"").append(ontology).append("\" :{\"StockPrice\" :{ ").
        append("\"timestamp\": {\"$date\": \"").append(timestampFormat.format(new Date(timestamp))).append("\"},").
 append("\"symbol\": \"").append(symbol).append("\",").
 append("\"name\": \"").append(name).append("\",").
 append("\"volume\": \"").append(volume).append("\",").
 append("\"stockExchange\": \"").append(stockExchange).append("\",").
 append("\"change\": \"").append(change).append("\",").
 append("\"daysLow\": \"").append(daysLow).append("\",").
 append("\"daysHigh\": \"").append(daysHigh).append("\",").
 append("\"lastTradePriceOnly\": \"").append(lastTradePriceOnly).append("\"}}}");
 
 return ontologyInstance.toString();
 }

}

El siguiente elemento que añadiremos a nuestra aplicación será una clase de utilidad, que nos proporicionará dos métodos. Uno para convertir el InputStream que se abre al conectar con el servicio externo de Yahoo, al enviar una petición de solicitud de cotizaciones, en un String con la respuesta del servicio. Y otro para permitir a nuestra App escribir ficheros con los que comunicar mensajes y logs al contenedor de KPs.


package com.indra.sofia2.demos.stock.util;

import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class Util {

 /**
 * Utilidad para convertir un InputStream en un String
 * @param is
 * @return
 */
 public static String getStringFromInputStream(InputStream is) {
    BufferedReader br = null;
    StringBuilder sb = new StringBuilder();
 
    String line;
    try {
 
      br = new BufferedReader(new InputStreamReader(is));
      while ((line = br.readLine()) != null) {
        sb.append(line);
      }
 
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
       if (br != null) {
         try {
           br.close();
         } catch (IOException e) {
            e.printStackTrace();
         }
       }
     }
 
     return sb.toString();
 }
 
 /**
 * Utilidad para crear ficheros de Mensajes o de Log
 * @param filename
 * @param lines
 * @throws IOException
 */
 public static void writeLinesInFile(String filename, String[] lines) throws IOException {
      PrintWriter pw = new PrintWriter(new FileWriter(filename));
 
       for (String line:lines) {
            pw.write(line+"\n");
       } 
       pw.close();
  }
}

La siguiente clase a programar será la que hará la petición al servicio externo de Yahoo para recibir los datos de cotizaciones. Se apoyará en la enumeración DowJonesStock para construir la query a enviar, y en la clase StockBean para una vez recibida la respuesta, mapearla a una lista de objetos con los datos recibidos por cada empresa:


package com.indra.sofia2.demos.stock.util;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Clase de utilidad que recubre el API de Yahoo para acceder a la información que nuestro KP necesita 
 * 
 *
 */
public class YahooApiWrapper {
 
 /**
 * Solicita al API de Yahoo las ultimas cotizaciones de las empresas del Dow Jones
 * @return
 * @throws IOException
 */
 public static List<StockBean> getDowJonesLastPrices(String yahooApiUrl) throws IOException{
   //Construye la sentencia YQL (Yahoo Query Language) que interroga a la tabla
   //de cotizaciones solititando todos los valores del Dow Jones
   StringBuilder query = new StringBuilder(
            "select * from yahoo.finance.quote where symbol in (");
 
   DowJonesStock[] stocks=DowJonesStock.values();
   for(int i=0;i<stocks.length;i++){
       DowJonesStock stock=stocks[i];
       query.append("'").append(stock.getSymbol()).append("'");
       if(i<stocks.length-1){
          query.append(","); 
       }
    }
    query.append(")");
 
   //Construye la petición HTTP al API. 
   //El resultado será una cadena JSON que incluirá un array con las cotizaciones
    String fullUrlStr = yahooApiUrl + "?q="+
               URLEncoder.encode(query.toString(), "UTF-8") +
                     "&format=json&diagnostics=true&"+
                     "env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=";
 
    URL fullUrl=new URL(fullUrlStr);
    InputStream is = fullUrl.openStream();
 
    String response=Util.getStringFromInputStream(is);
    is.close();
 
   //Convierte la respuesta de la petición al API en una lista de objetos StockBean,
   // cada uno representando la cotización de una empresa
   ObjectMapper mapper=new ObjectMapper();
   Map<String, Map<String, Map<String, List<Map<String, String>>>>> objResponse = 
            mapper.readValue(response, Map.class);
 
   List<Map<String, String>> result=objResponse.get("query").get("results").get("quote");
 
   List<StockBean> stocksList=new ArrayList<StockBean>();
   for(Map<String, String> stockPrice:result) {
       StockBean newStock=new StockBean();
       newStock.setTimestamp(System.currentTimeMillis());
       newStock.setSymbol(stockPrice.get("symbol"));
       newStock.setName(stockPrice.get("Name"));
       newStock.setVolume(stockPrice.get("Volume"));
       newStock.setStockExchange(stockPrice.get("StockExchange"));
       newStock.setChange(stockPrice.get("Change"));
       newStock.setDaysLow(stockPrice.get("DaysLow"));
       newStock.setDaysHigh(stockPrice.get("DaysHigh"));
       newStock.setLastTradePriceOnly(stockPrice.get("LastTradePriceOnly"));
 
       stocksList.add(newStock);
    }
 
 
    return stocksList;

 }

}

Finalmente solo falta programar el punto de entrada a nuestro KP, es decir, el método Main al que se invocará en cada ejecución, y que orquesta el flujo de trabajo para primero, invocar al servicio externo de datos programado en la clase YahooApiWrapper, y después, convertir la respuesta ya mapeada a una lista de objetos de tipo StopBean, en un fichero con mensajes en formato ontológico que el contenedor de KPs deberá enviar al SIB.


package com.indra.sofia2.demos.stock;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

import com.indra.sofia2.demos.stock.util.StockBean;
import com.indra.sofia2.demos.stock.util.Util;
import com.indra.sofia2.demos.stock.util.YahooApiWrapper;

/**
 * Punto de entrada a nuestra App
 * Controla la ejecución del KP 
 *
 */
public class StockEntryPoint {
 
   //Nombre de la propiedad que nos dará la url del servicio externo de la que recuperamos
   //la información de cotización
   private final static String YAHOO_API_URL="yahooApiUrl";
 
   //Nombre de nuestra ontologia
   private final static String ONTOLOGY_NAME="StockPrice";
 
   //Prefijo de ficheros indicado en la consola de Administración para
   //que el contenedor pueda localizar los ficheros de mensajes 
   private final static String MESSAGE_FILES_PREFIX="DowJonesMessages";
 
   //Prefijo de ficheros indicado en la consola de Administración para
   //que el contenedor pueda localizar los ficheros de log
   private final static String LOG_FILES_PREFIX="DowJonesLog";
 
   public static void main(String args[]){
       try{
         //Carga las propiedades del programa
         Properties prop = new Properties();
         InputStream input = new FileInputStream("DowJonesDataFeed.properties"); 
         prop.load(input);
 
         //Recupera del servicio web los ultimos valores de cotización 
         //para las empresas del Dow Jones
         List<StockBean> lastStockPrices=
            YahooApiWrapper.getDowJonesLastPrices(prop.getProperty(YAHOO_API_URL));
 
         //Convierte dichas cotizaciones en instancias de nuestra ontologia
         String[] messages=new String[lastStockPrices.size()];
         int i=0;
         for(StockBean stockPrice:lastStockPrices){
             messages[i++]=stockPrice.getAsOntologyInstance(ONTOLOGY_NAME);
         }
 
         //Crea un fichero con dichas instancias de nuestra ontologia para
         // que el contenedor de KPs los envie al SIB
         String filename=MESSAGE_FILES_PREFIX+((int)(Math.random()*10000));
         Util.writeLinesInFile(filename, messages);
 
       }catch(Exception e){
            try {
                Util.writeLinesInFile(LOG_FILES_PREFIX, 
                       new String[]{"Error in programm: "+e.getMessage()});
            } catch (IOException e1) {
            }
      }
 }
 
 
 
}

4. Empaquetado del KP:

Al tratarse de una App Java, el contenedor necesita que sea empaquetado como un fichero JAR ejecutable. Esto es, todas las clases que no formen parte del Runtime de Java y que sean necesarias para la ejecución del programa deben incluirse dentro del propio fichero JAR, así como un fichero MANIFEST.MF indicando el punto de entrada (Clase con el método Main).

En el caso de nuestra App, ominitiendo las clases de las librerías de terceros utilizadas, la estructura sería la siguiente:

5. Despliegue en Contenedor de KPs:

Finalmente daremos de alta el KP en el contenedor de KPs. Para ello es necesario ser administrador de Sofia2 o solicitar el despliegue a un administrador.

Una vez autenticados en la consola de administración, elegimos la opción de menú Contenedor de KPs/Apps > Crear KPs/Apps en contenedor. E introducimos la siguiente información:

  • KP: DowJonesStockFeed (Creado en paso 2).
  • Instancia de KP: DowJonesDataFeed (podría ser cualquier otro)
  • Token: El que tenemos dado de alta para el KP.
  • Tipo de KP: Temporizado
  • Cron: 0 0/1 * 1/1 * ? * (Cada minuto, podría ser cualquier otra expresión en función de nuestras necesidades)
  • Lenguaje: Java
  • Programa: Subimos desde nuestra máquina el fichero JAR que empaquetamos en el paso 4.
  • Nombre de programa: DowJonesDataFeed (podría ser cualquier otro)
  • Timeout: 5sg (Daremos algo de margen al tener que invocar a un servicio externo sujeto a retrasos)
  • Prefijo de ficheros de mensajes: DowJonesMessages (Es el prefijo que utilizamos en nuestro código para generar este tipo de ficheros).
  • Perfijo de ficheros de Log: DowJonesLog (Es el prefijo que utilizamos en nuestro código para generar este tipo de ficheros).

  • Descripción: Texto Libre
  • Propiedades: Damos de alta la propiedad yahooApiUrl con la url del API al que haremos las consultas de cotización, que recuperamos en el programa.

6. Comprobación del estado del KP en el contenedor:

Una vez desplegado el KP en el contenedor, podemos monitorizar su estado para ver que está arrancando e insertando información correctamente desde la opción Visualizar:

Así como consultar a la BDTR para consultar las cotizaciones insertadas:

Creando un KP para el contenedor de KPs de Sofia2

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s