Expanding the EMA Java Consumer example to provide web services: REST

the Elektron Message API (EMA) was designed to be an easy to learn and easy to use, high level, high performance API. Now, we are going to demonstrate how to enhance its featureset to be able to support a request from a remote system or machine via web service.

In this article, we will focus on the REST technology which gains more popularity nowadays. For an example that uses older SOAP technology, please click here.

What is a REST Web Service?

REST (short for REpresentational State Transfer) or RESTful web service is an architectural style defined to help create and organize distributed systems. Comparing to the rival web service technology, REST technology works on top of the HTTP protocol, provides simpler implementation & maintenance, and allows a greater variety of data formats, whereas SOAP only allows XML, and control data-structure via WSDL.

REST Web Service Architecture

REST does not contain or define a structure or header. It focuses on design rules for creating stateless services. A client can access the resource using the unique URI and a representation of the resource is returned. A client can access RESTful resources with HTTP protocol, the URL of the resource serves as the resource identifier and GET, PUT, DELETE, POST and HEAD are the standard HTTP operations to be performed on that resource.

Software & tools required

  • Java EE
  • Eclipse IDE
  • Apache Tomcat 8.5
  • Apache Axis2
  • Elektron SDK Java

Step 1: Setup developing environment

This section is for a new developer who has never had experience with the Eclipse IDE or Apache Tomcat Server before.

First of all, you need to have a Java runtime installed on your machine. If you don't have one, please download and install it from the Oracle website. The recommended Java version is 7 or higher (required by EMA Java libraries).

Then, you need to have Eclipse IDE for Java EE Developers, and we also use  Apache Tomcat 8.5 in this demonstration as well.

 

Once you firstly open Eclipse IDE, specify a target workspace directory and click OK

Then, you need to set the Server Runtime Environments.

Navigate to menu > Window > Preferences. Type 'runtime', and find Runtime Environments at the left pane.

Click the Add button and select Apache Tomcat v8.5, mark the Create a new local server check box. Then, click Next button.

Specify Tomcat installation directory to the location that you installed Apache Tomcat 8.5 on your machine. Click the Finish button to exit the New Server Runtime Environment window.

Then, click the OK button. Now, you are ready to develop a new application.

Remark: Once you add the Tomcat server, it will show in the Project Explorer tab, and Servers tab.

Step 2: Create an EMA Java-based project

First of all, you need to have Elektron SDK Java package. You can find it from Thomson Reuters Developer Community: Elektron SDK - Java (Download). Once you get the file, extract the package to a location desired.

 

Since Web Services Technologies are available in Java EE platforms, so we need to create a Web project instead of a normal Java Project by navigating to menu > File > New > Dynamic Web Project. Then, a New Dynamic Web Project window appears.

Name the project 'EMAJavaRESTWebService', select Target runtime to Apache Tomcat v8.5 server (from step 1). Then, click the Finish button.

Right-click at the EMAJavaRESTWebService project, and select Import Import...

In the Import window, choose General File System, and click the Next > button.

Browse the location of Elektron SDK Java that you extracted the package earlier. Then, select all subfolders except Docs both of Ema and Eta folders, and click the Finish button

After you imported the previous files/folders, they will appear under the EMAJavaRESTWebService project.

Next, we are going to run and test one of the EMA sample application; example102_MarketPrice_Snapshot. Start with creating a new empty package under EMAJavaRESTWebService/Java Resources/src folder.

When the New Java Package window shows up, put a package name; com.thomsonreuters.ema.articles.webservices.common, and click the Finish button.

Then, navigate to the imported folders, go to Ema Src examples java com thomsonreuters ema examples training consumer series100 example102_MarketPrice_Snapshot, and copy Consumer.java to the com.thomsonreuters.ema.articles.webservices.common package created previously.

 

You'll notice that there are a lot of compilation errors in the Consumer.java file. We are going to solve the errors step-by-step.

  • Click the yellow light bulb in front of the package com.thomsonreuters.ema.examples.training.consumer.series100.example102__MarketPrice__Snapshot; statement, choose com.thomsonreuters.ema.articles.webservices.common instead.

  • Right-click at the EMAJavaRESTWebSerice project, select the Properties menu.

  • In the Properties for EMAJavaRESTWebService window, click Java Build Path on the left pane. On the right pane, click Libraries tab, and then click the Add Jars... button.

  • Then, select the following jar files under Ema/Libs and Eta/Libs folders, and click OK button to exit the JAR Selection window.

  • The list of selected jar files will show up, click OK button to exit the Properties for EMAJavaRESTWebService window.

  • When you save the Consumer.java file in the com.thomsonreuters.ema.articles.webservices.common package, the compilation errors will disappear.

After fixing all the errors, the next step is to copy required configuration and data dictionary files to the root folder. Find enumtype.defRDMFieldDictionay (under the Ema/etc folder) and EmaConfig.xml (under Ema/Src/examples/java), and copy them to the root EMAJavaRESTWebService project.

These files will show up under the root level project.

Before running the Consumer.java file, you need to change its configuration first.

Double click the EmaConfig.xml file, click the Source tab and edit a Consumer_1 configuration to use local dictionary files. Then, save it. The application will read dictionary data from the local files instead.

Here this is the snippet code where Dictionary_2 points to.

		<Dictionary>
			<Name value="Dictionary_2"/>
			<DictionaryType value="DictionaryType::FileDictionary"/>

			<!-- dictionary names are optional: defaulted to RDMFieldDictionary and enumtype.def -->
			<RdmFieldDictionaryFileName value="./RDMFieldDictionary"/>
			<EnumTypeDefFileName value="./enumtype.def"/>
		</Dictionary>

 

Back to the Consumer.jar file, change the destination hostname, and the service name corresponding to your server, and save it.

 

To run the Consumer.java, right-click this file, select Run As > Java Application

Check the output result in the Console tab. If there is no problem, the application should show a subscription result without any error messages.

Here this is the example log output:

Oct 30, 2017 2:31:17 PM com.thomsonreuters.ema.access.ChannelCallbackClient reactorChannelEventCallback
INFO: loggerMsg
    ClientName: ChannelCallbackClient
    Severity: Info
    Text:    Received ChannelUp event on channel Channel_1
	Instance Name Consumer_1_1
	Component Version ads2.6.0.L1.solaris.rrg 64-bit
loggerMsgEnd


RefreshMsg
    streamId="5"
    domain="MarketPrice Domain"
    solicited
    RefreshComplete
    state="Non-streaming / Ok / None / 'All is well'"
    itemGroup="00 00"
    permissionData="03 08 43 65 62 c0"
    name="TRI.N"
    nameType="1"
    serviceId="2115"
    serviceName="API_ELEKTRON_EPD_RSSL"
    Payload dataType="FieldList"
        FieldList FieldListNum="79" DictionaryId="1"
            FieldEntry fid="1" name="PROD_PERM" dataType="UInt" value="6562"
            FieldEntry fid="2" name="RDNDISPLAY" dataType="UInt" value="64"
            FieldEntry fid="3" name="DSPLY_NAME" dataType="Rmtes" value="THOMSON REUTERS"
            FieldEntry fid="4" name="RDN_EXCHID" dataType="Enum" value="2"
            FieldEntry fid="6" name="TRDPRC_1" dataType="Real" value="47.02"
            FieldEntry fid="7" name="TRDPRC_2" dataType="Real" value="47.02"
            FieldEntry fid="8" name="TRDPRC_3" dataType="Real" value="47.0"
            FieldEntry fid="9" name="TRDPRC_4" dataType="Real" value="47.0"
            FieldEntry fid="10" name="TRDPRC_5" dataType="Real" value="47.0"
            FieldEntry fid="11" name="NETCHNG_1" dataType="Real" value="-0.34"
            FieldEntry fid="12" name="HIGH_1" dataType="Real" value="47.27"
            FieldEntry fid="13" name="LOW_1" dataType="Real" value="46.83"
            FieldEntry fid="14" name="PRCTCK_1" dataType="Enum" value="1"
            FieldEntry fid="15" name="CURRENCY" dataType="Enum" value="840"
            FieldEntry fid="16" name="TRADE_DATE" dataType="Date" value="27 OCT 2017 "
            FieldEntry fid="18" name="TRDTIM_1" dataType="Time" value="20:01:00:000:000:000"
            FieldEntry fid="19" name="OPEN_PRC" dataType="Real" value="47.27"
            FieldEntry fid="21" name="HST_CLOSE" dataType="Real" value="47.36"
            FieldEntry fid="22" name="BID" dataType="Real" value="47.0"
            FieldEntry fid="23" name="BID_1" dataType="Real" value="47.0"
            FieldEntry fid="24" name="BID_2" dataType="Real" value="47.0"
            FieldEntry fid="25" name="ASK" dataType="Real" value="47.01"
            FieldEntry fid="26" name="ASK_1" dataType="Real" value="47.01"
            ...and more fields
        FieldListEnd
    PayloadEnd
RefreshMsgEnd

 

Step 3: Prepare Web Service auxiliary classes

After we test run the EMA Java example in the com.thomsonreuters.ema.examples.training.consumer.series100.example102__MarketPrice__Snapshot package to ensure that your development machine can use EMA Java to connect to the server and get data, the next step is that we design an application that consists of three classes as follows:

  • InstrumentData is a POJO (Plain old Java object) class which is used to store instrument data from the subscription.
  • ConnectionContext represents a connection establishment and provides an OmmConsumer instance that encapsulates subscription functionality.
  • SnapshotRestService is the main interface class. It will receive requests and send responses to clients.

 

We'll start with creating a new InstrumentData class under the com.thomsonreuters.ema.articles.webservices.common package by right clicking the package, New Class.

There will be a New Java Class window appears, put the class name as InstrumentData, click the Finish button to exit this window.

As InstrumentData will be used to store instrument record and fields. Here this is the example source code of this class to keep a RIC name, subscription status, and fields' data (e.g. BID, ASK).

package com.thomsonreuters.ema.articles.webservices.common;

public class InstrumentData {
	public static final String LINEBREAK = "\n";
	public static final String TAB = "\t";
	private String ric;
	private String streamStatus;
	private String statusText;
	private String bid;
	private String ask;
	
	public String getRic() {
		return ric;
	}
	public void setRic(String ric) {
		this.ric = ric;
	}
	public String getStreamStatus() {
		return streamStatus;
	}
	public void setStreamStatus(String streamStatus) {
		this.streamStatus = streamStatus;
	}
	public String getStatusText() {
		return statusText;
	}
	public void setStatusText(String statusText) {
		this.statusText = statusText;
	}
	public String getBid() {
		return bid;
	}
	public void setBid(String bid) {
		this.bid = bid;
	}
	public String getAsk() {
		return ask;
	}
	public void setAsk(String ask) {
		this.ask = ask;
	}
	public String toString() {		
		StringBuffer sb = new StringBuffer();
		sb.append("RIC:");
		sb.append(TAB);
		sb.append(ric);
		sb.append(LINEBREAK);
		sb.append("Status:");
		sb.append(TAB);
		sb.append(streamStatus);
		sb.append("/");
		sb.append(statusText);
		sb.append(LINEBREAK);
		sb.append("Fields:");
		sb.append(LINEBREAK);
		sb.append(TAB);
		sb.append("BID: ");		
		sb.append(bid);
		sb.append(LINEBREAK);
		sb.append(TAB);
		sb.append("ASK: ");		
		sb.append(ask);
		sb.append(LINEBREAK);
		
		return sb.toString();
	}
}

 

Repeat the step to create a new class for the ConnectionContext class.

For this ConnectionContext class, it will initialize an OmmConsumer instance by giving a server host and DACS user (if required).

package com.thomsonreuters.ema.articles.webservices.common;

import com.thomsonreuters.ema.access.EmaFactory;
import com.thomsonreuters.ema.access.OmmConsumer;
import com.thomsonreuters.ema.access.OmmConsumerConfig;
import com.thomsonreuters.ema.access.OmmException;

public class ConnectionContext {
	public static final String HOST = "<<HOST:PORT>>";
	public static final String USER = "<<DACS_USER>>";
	private static OmmConsumer consumer = null;
	
	static {
		try
		{
			OmmConsumerConfig config = EmaFactory.createOmmConsumerConfig();
			consumer  = EmaFactory.createOmmConsumer(config.host(HOST).username(USER));
		}
		catch (OmmException excp)
		{
			excp.printStackTrace();
		}
	}
	
	private ConnectionContext() {}
	
	public static OmmConsumer getConsumer() {
		return consumer;
	}
	
}

 

In this ConnectionContext class, it will establish a connection using information defined in the string variables hard-coded.

Then, repeat the step to create a new class named SnapshotRESTService under a new package named com.thomsonreuters.ema.articles.webservices.rest.

This SnapshotRESTService class will receive a RIC parameter, contact ConnectionContext to use OmmConsumer, subscribe to data, and return a subscription result using an instance of InstrumentData.

package com.thomsonreuters.ema.articles.webservices.rest;

import java.util.Iterator;
import java.util.concurrent.TimeoutException;

import com.thomsonreuters.ema.access.AckMsg;
import com.thomsonreuters.ema.access.DataType;
import com.thomsonreuters.ema.access.EmaFactory;
import com.thomsonreuters.ema.access.FieldEntry;
import com.thomsonreuters.ema.access.GenericMsg;
import com.thomsonreuters.ema.access.Msg;
import com.thomsonreuters.ema.access.OmmConsumer;
import com.thomsonreuters.ema.access.OmmConsumerClient;
import com.thomsonreuters.ema.access.OmmConsumerEvent;
import com.thomsonreuters.ema.access.RefreshMsg;
import com.thomsonreuters.ema.access.ReqMsg;
import com.thomsonreuters.ema.access.StatusMsg;
import com.thomsonreuters.ema.access.UpdateMsg;
import com.thomsonreuters.ema.articles.webservices.common.ConnectionContext;
import com.thomsonreuters.ema.articles.webservices.common.InstrumentData;

public class SnapshotRESTService {
	public static final int TIMEOUT = 5;
	public static final String SERVICE_NAME = "<<SERVICE_NAME>>";
	private InstrumentData data = new InstrumentData();
	
	public synchronized InstrumentData subscribe(String ric) {
		synchronized (data) {
			AppClient appClient = new AppClient();
			OmmConsumer consumer = ConnectionContext.getConsumer();
			ReqMsg reqMsg = EmaFactory.createReqMsg();
			reqMsg.clear();
			
			try {
				if (consumer == null) throw new RuntimeException("Connection: Cannot retrieve OmmConsumer instance. Unable to make a connection to server.");
				consumer.registerClient(reqMsg.serviceName(SERVICE_NAME).name(ric).interestAfterRefresh(false), appClient);
				data.wait(TIMEOUT * 1000);
				if (data == null) throw new TimeoutException("Timeout: Cannot retrieve data within " + TIMEOUT + " seconds");
			} catch (Exception e) {
				data.setRic(ric);
				data.setStreamStatus("SUSPECT");
				data.setStatusText(e.getClass().getName() + ":" + e.getMessage());
			}
			return data;
		}
	}
	
	class AppClient implements OmmConsumerClient
	{
		public static final int FID_BID = 22;
		public static final int FID_ASK = 25;
		public void onRefreshMsg(RefreshMsg refreshMsg, OmmConsumerEvent event)
		{
			synchronized (data) {
				data.setRic(refreshMsg.name());
				data.setStreamStatus(refreshMsg.state().dataStateAsString());
				data.setStatusText(refreshMsg.state().statusText());
				if (DataType.DataTypes.FIELD_LIST == refreshMsg.payload().dataType()) {
					Iterator<FieldEntry> iter = refreshMsg.payload().fieldList().iterator();
					FieldEntry fieldEntry;
					while (iter.hasNext())
					{
						fieldEntry = iter.next();
						if (fieldEntry.fieldId() == FID_BID) {
							data.setBid(fieldEntry.load().toString());
							continue;
						} else if (fieldEntry.fieldId() == FID_ASK) {
							data.setAsk(fieldEntry.load().toString());
							continue;
						} else if (data.getBid() != null && data.getAsk() != null) break;
					}
				}
				data.notify();
			}
		}

		public void onStatusMsg(StatusMsg statusMsg, OmmConsumerEvent event) 
		{
			data.setRic(statusMsg.name());
			data.setStreamStatus(statusMsg.state().dataStateAsString());
			data.setStatusText(statusMsg.state().statusText());
		}
		
		public void onUpdateMsg(UpdateMsg updateMsg, OmmConsumerEvent event) {}
		public void onGenericMsg(GenericMsg genericMsg, OmmConsumerEvent consumerEvent){}
		public void onAckMsg(AckMsg ackMsg, OmmConsumerEvent consumerEvent){}
		public void onAllMsg(Msg msg, OmmConsumerEvent consumerEvent){}
	}
}

 

You can also perform a unit test by running this class as a standalone application as well. For this case, we create SnapshotRESTServiceTest, and print a result from SnapshotRESTService.subscribe() method.

package com.thomsonreuters.ema.articles.webservices.test;

import com.thomsonreuters.ema.articles.webservices.common.InstrumentData;
import com.thomsonreuters.ema.articles.webservices.rest.SnapshotRESTService;

public class SnapshotRESTServiceTest {
	public static void main(String[] args) {
		SnapshotRESTService obj = new SnapshotRESTService();
		InstrumentData result = obj.subscribe("TRI.N");
		System.out.println(result);
	}
}

 

Here this is the result when it can get data successfully.

Oct 30, 2017 3:24:54 PM com.thomsonreuters.ema.access.ChannelCallbackClient reactorChannelEventCallback
INFO: loggerMsg
    ClientName: ChannelCallbackClient
    Severity: Info
    Text:    Received ChannelUp event on channel Channel_1
	Instance Name Consumer_1_1
	Component Version ads2.6.0.L1.solaris.rrg 64-bit
loggerMsgEnd


RIC:	TRI.N
Status:	Ok/All is well
Fields:
	BID: 47.0
	ASK: 47.01

Step 4: Integrate Web Service and test a result

JAX-RS (Java API for RESTful Web Services) is a Java programming language API spec that provides support in creating web services according to the Representational State Transfer architectural pattern. In this article, we use Jersey as a JAX-RS reference implementation, which seamlessly supports exposing data in a variety of representation media types and abstract away the low-level details of the client-server communication. Once you obtain a zip file, it contains many jar files as follows:

 

After that, we will map these files to be a single user library by right-clicking the EMAJavaRESTWebService project and select the Properties menu. Once the Properties for EMAJavaRESTWebService window appears, click the Libraries tab, then click the Add Library... button.

An Add Library window will appear, select User Library and click the Next > button.

On the next page, click the User Libraries... button, there will be a new window named Preferences (Filtered) appears. Then, click the New... button.

Input the new user library name: JerseyRESTfulWebService. Click the OK button to go back to the previous window.

Please make sure that you are selecting the JerseyRESTfulWebService created previously. Then, click the Add External JARs... button.

Add all jar files under the subfolder extracted from the Jersey zip file.

After finish adding jars, they will appear under the JerseyRESTfulWebService user library.

Apply all settings, until you go to the base project's properties setup window. Then, click the Delopyment Assembly menu on the left panel.

On this Web Deployment Assembly setting option, Click the Add... button.

On a New Assembly Directive window, select Java Build Path Entries option and click the Next > button.

Select all entries and click the Finish button to exit this New Assembly Directive window.

Click the Apply and Close button to exit this Properties for EMAJavaRESTWebService window.

Once you finishing setting all libraries for this tutorial, Open the SnapshotRESTService and add more code as depicted below:

Here this is the snippet code:

// at import section
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;


// at the line above SnapshotRESTService
@Path("/SnapshotRESTService")

// add more three following methods.
	@Path("/subscribeTextPlain/{ric}")
	@GET
	@Produces(MediaType.TEXT_PLAIN)
	public String subscribeTextPlain(@PathParam("ric") String ric) {
		return subscribe(ric).toString();
	}
	
	@Path("/subscribeJson/{ric}")
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public InstrumentData subscribeJson(@PathParam("ric") String ric) {
		return subscribe(ric);
	}
	
	@Path("/subscribeXml/{ric}")
	@GET
	@Produces(MediaType.APPLICATION_XML)
	public InstrumentData subscribeXml(@PathParam("ric") String ric) {
		return subscribe(ric);
	}

 

The  @Path("/SnapshotRESTService"),  @Path("/subscribeTextPlain/{ric}"),  @Path("/subscribeJson/{ric}"), and @Path("/subscribeXml/{ric}") statements are annotations to be resources path for the EMAJavaRESTWebService application. So, if a client requests /EMAJavaRESTWebService/SnapshotRESTService, it will access to this SnapshotRESTService class file. But if the client requests more specifically such as /EMAJavaRESTWebService/SnapshotRESTService/subscribeXml/TRI.N, the request will invoke SnapshotRESTService.subscribeXml method with TRI.N as the ric argument.

 

Referring to the new methods previously, they provide a response in a particular format corresponding to the name (plain text, JSON, and XML). For the xml response type, it requires a special code in the InstrumentData file to convey the top root level of the return type.

package com.thomsonreuters.ema.articles.webservices.common;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class InstrumentData {
	public static final String LINEBREAK = "\n";
...

Last but not least, you need to edit the Deployment Descriptor file -- web.xml to make Jersey manages REST requests by selecting Deployment Descriptor: EMAJavaRESTWebService, and add the following lines into this file.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
  <display-name>EMAJavaRESTWebService</display-name>
  <servlet>
  	<servlet-name>Jersey REST Service</servlet-name>
  	<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
  	<init-param>
  		<param-name>jersey.config.server.provider.packages</param-name>
  		<param-value>com.thomsonreuters.ema.articles.webservices.rest</param-value>
  	</init-param>
  	<load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
  	<servlet-name>Jersey REST Service</servlet-name>
  	<url-pattern>/rest/*</url-pattern>
  </servlet-mapping>
</web-app>

 

The result will look like the similar figure.

Now, you are ready to test this EMAJavaRESTWebService application. Right-click at the project, select Run as > Run on the server

When the Run On Server window appears, select the Tomcat v8.5 Server at localhost (configured at step 1 or other desired servers), then click the Finish button.

Eclipse will open http://localhost:8080/EMAJavaRESTWebService using its native web browser engine. However, it will encounter HTTP Status 404 - Not Found, but don't worry. This error happens because we haven't created any HTML files in this project. We recommend you use another web browser (e.g. Chrome) to demonstrate the result.

You can go to http://localhost:8080/EMAJavaRESTWebService/rest/application.wadl to see the list of services generated by Jersey.

As described earlier, you can get content results in various formats.