About this article

The intended audiences of this article are software developers/engineers interested in or are currently developing a provider application to publish Symbol List. The article explains how to leverage Elektron Message API to write a Non-Interactive Provider application that publishes Symbol List.

The Elektron Message API(EMA) is a data-neutral, multi-threaded, ease-of-use API providing access to OMM and RWF data. As part of the Elektron Software Development Kit, or Elektron SDK/Elektron APIs, the Elektron Message API allows applications to consume and provide OMM data at the message level of the API stack. The message level is set on top of the transport level which is handled by the Elektron Transport API (also known as the UPA). To understand EMA better e.g. concept, functionality and capabilities, please refer to API Concepts Guide

Symbol List Overview

The Symbol List is a domain model defined by Thomson Reuters to provide access to a set of symbol names, typically from an index, service, or cache. Data of this domain must contain symbol names and optionally can contain additional cross-reference information such as permission information, name type, or other venue-specific content.

Basic scenario of Symbol List is that a consumer wants to open multiple items but doesn’t know their names, the consumer can first issue a request using a Symbol List. If there is a provider exists that can resolve the symbol list name into a set of item names, the provider sends back only symbol names back.  

The Symbol List domain is a Reuters Domain Model (RDM) defined by Thomson Reuters to be used by all internal, and third-party, service providers and consumers. Clients that want to achieve maximum interoperability with these applications should utilize the Reuters Domain Models. The definition of RDMs is described in the RDM Usage Guide document. Therefore, applications can still interoperate as long as they conform to the item type models.

Like other domain models, the response message of Symbol List can be Refresh, Update and Status. 

  • The Refresh message sends a list of item names to consumer. Symbol List refresh can be sent in multiple parts that large symbol list information can be split into smaller and, more manageable pieces.
  • The Update message adds or removes items from the list including update cross-reference information. 
  • The Status message conveys state change information associated with an item stream.

Content of Symbol List response is encoded as a Map, with each symbol represented by a map entry and where the symbol name is the entry key. The key’s type is ASCII string which should contain only characters that are valid in ASCII specification. An entry’s payload is optional, but when present the payload is a FieldList that contains additional cross-reference information such as permission information, name type, or other venue-specific content.  As the Map Entry’s payload is optional, the entry’s payload can be empty.

Below is the sample structure of symbol list that an entry’s payload contains cross-reference information. MapEntry’s payload is a FieldList that contains the following information: 

  • PROV_SYMB (3422): Original symbol provided by the exchange 
  • PROD_PERM (1): Permission information 

Each entry has an associated action which informs Consumer of how to apply the information contained in the entry. For Provider, the entry action helps to manage change on list of symbols. Below are the possible actions for Map Entry.

  • ADD - Indicates that the consumer should add the entry. An add action typically occurs when an entry is initially provided. 
  • UPDATE - Indicates that the consumer should update any previously stored or displayed information with the contents of this entry. An update action typically occurs when an entry has already been added and changes to the contents need to be conveyed.
  • DELETE - Indicates that the consumer should remove any stored or displayed information associated with the entry. No map entry payload is included when the action is DELETE.

Please note that the order in which the Map Entries will be delivered within Map is not guaranteed. The order may be changed after the data pass through components such as TREP. If order of entry is necessary, additional Field ID (i.e. RANK_POS) can be added to Field List of Map Entry’s payload so Consumer can use the field for Entry sorting.

Map can contain additional Summary data which is the same type as Map Entry’s payload. The Summary data contains common information shared between Entrys. Provider optionally can add shared information of Entry or Symbol List information in the Summary data.

ฺTo better understand the SymbolList structure, below are illustrations for sample Refresh and Update messages.

  

Publisher / Provider Concepts

If you would like to expose a specific data service to consumer applications in this article is Symbol List data, building a Provider application should be considerred. Provider applications are able to connect to a TREP or Elektron infrastructure and to leverage its real-time and streaming distribution system to publish data. This is an efficient solution to make your own set of capabilities (e.g. content, workflow, etc.) available to your consumer applications.

Provider applications can be either interactive or non interactive. Anyway, this article focuses on Non Interactive Providers only.

  • Interactive Providers (IP) receive requests from the infrastructure and must respond interactively to these requests. Via these requests, the infrastructure may ask for information about the provided services and capabilities of the provider. The infrastructure also forwards consumer requests demanding for specific data items. For all these requests, the Interactive Provider must respond interactively by either providing the requested information or by rejecting the demand. 
  • Non Interactive Providers (NIP) are not solicited by infrastructure requests but instead they publish information (e.g. Available services, data items…) following their own timing. In this model Thomson Reuters Enterprise Platform(TREP) caches the published information and serves it to the demanding consumer applications as shown in the NIP Feature Diagram below:

Therefore, NIP can publish Symbol List into the ADH cache without needing to handle/wait for requests from any consumer applications. Whenever a consumer application sends a request for Symbol List to Advanced Distribution Server(ADS) which is a component of TREP, ADS forwords the request to ADH. Finally, ADH provides Symbol List kept in the cache which is received from NIP previously to the consumer applcation.

Solution

Prerequisites

  1. Download an Elektron SDK Package
  1. Declare the service sent by the NIP in TREP configuration files. This process must be executed by your TREP administration team. For information, please refer to TREP Configuration for NI Providers which explains the steps involved in this process. If TREP administration team requires any assistance regarding configuring TREP, they can submit the query to TREP support team directly via Contact US in My Account. In this article, we will use the service named NI_PUB which is one of the default service configured in EmaConfig.xml shipped with Elektron SDK Package. 

     
  2. Put EmaConfig.xml shipped with Elektron SDK package in the working directory. Then, modify it to:
    • Add Symbol List capabilities
    • Set EMA to use local dictionary files 

            As an example below:

<NiProvider>
      <Name value="Provider_1"/>
	  …
      <Directory value="Directory_1"/>
…
<Directory>
      <Name value="Directory_1"/>
      …
      <Service>
         <Name value="NI_PUB"/>	
         <InfoFilter>
            <DictionariesProvided>
	 	       <DictionariesProvidedEntry value="Dictionary_3"/>
	        </DictionariesProvided>
	        <DictionariesUsed>
		       <DictionariesUsedEntry value="Dictionary_3"/>
	        </DictionariesUsed>
            …
            <Capabilities>
		       …
			   <CapabilitiesEntry value="MMT_SYMBOL_LIST"/>
		    </Capabilities>
…
<Dictionary>
      <Name value="Dictionary_3"/>
      …
      <RdmFieldDictionaryFileName value="C:\Elektron-SDK\Ema\etc\RDMFieldDictionary"/>
      <EnumTypeDefFileName value="C:\Elektron-SDK\Ema\etc\enumtype.def"/>

 

Building a Non-Interactive Provider to publish Symbol List

The general process of building NIP to publish Symbol List can be summarized in the following steps:

To make a simple NIP and focus on publishing Symbol List, we will let EMA performs the following steps internally automatically according to EMA configuration file, EmaConfig.xml:

  • The 3th step which is loading dictionary locally. Please refer to the No.3 in the section Prerequisites how to set this. Alternatively, NIP can download dictionary from ADH instead. This is demonstrated in example350__Dictionary__Streaming application in Elektron SDK Java Packages or 350__Dictionary__Streaming application in Elektron SDK C++ Packages
  • The 4th step which is providing Source Directory Information to ADH. The article uses NI_PUB service which is decleared in EmaConfig.xml already. Instead, the application can perform this by itself as demonstrated in example300__MarketPrice__Streaming in Elektron SDK Java Packages or 300__MarketPrice__Streaming in Elektron SDK C++ Packages

According to the step 5th, to publish a Symbol List Refrsh or Update message properly, you should follow the guideline below:

  1. Initially, Provider publishes Refresh messages which contain all Symbol names in MapEntry’s key. All MapEntry’s action is ADD. Provider optionally can split list of symbol to be sent in multiple Refresh responses. 
  2. In case that symbols are removed from the list, provider publishes Update message which contains removed symbols in MapEntrys. The Entry’s action is DELETE. The MapEntry’s payload needs to be empty.
  3. In case that symbols are added to the list, provider publishes Update message which contains added symbols in MapEntrys. The Entry’s action is ADD.
  4. In case that cross-reference information of symbols is changed, provider publishes Update message which contains updated symbols in MapEntrys. The Entry’s action is UPDATE. The Entry’s payload contains updated FieldList.

Source Code

This section is to explain how to develop an NIP application using EMA Java and C++ to publish Symbol List. Our example application contains 2 classes:

  • SymbolListMapEntry class. This is an utility class. It represents an entry in the map of Symbol List. Each entry consists of Action, PROD_PERM and PROV_SYMB field as shown in the figure below:

            

  • NiProvider class. It is the main class of this application. It publishes Symbol List Refresh and Update messages to ADH. It uses SymbolListMapEntry class to help to create the Symbol List Map.  

The overview steps to develop the application are listed below:

  1. Create SymbolListMapEntry class in SymbolListMapEntry.java/.h/.cpp file. Add Action, PROD_PERM and PROV_SYMB field of an entry and the methods to get the fields. Then, create the methods which performs Add, Delete and Update action for the entry as example shown below: 
  • EMA Java

   import com.thomsonreuters.ema.access.MapEntry;

   	//The class is to represent an entry in the map of Symbol List.
   public class SymbolListMapEntry {
   		//Action can be UPDATE(1), ADD(2) or DELETE(3)
   		private int action; 
		//PROD_PERM  which field id is 1 and type is UINT64 
		private int prodPerm; 
		//PROV_SYMB  which field id is 3422 and type is RMTES_STRING
		private String provSymb;
		private static final int DEFAULT_PROD_PERM=3056;
		int getAction() {
			return action;
		}
		int getProdPerm() {
			return prodPerm;
		}
		String getProSymb() {
			return provSymb;
		}
		static int getDefaultProdPerm() {
			return DEFAULT_PROD_PERM;
		} 
		//For ADD action
		//Set PROD_PERM and PROV_SYMB according to the input values
		//If the input PROD_PERM is less than zero set to default(3056)
		//Set Action to be ADD
		void performAddAction(int newpp,String newps) {
			if(newpp<=0)
				newpp = DEFAULT_PROD_PERM;
			prodPerm = newpp;
			provSymb = newps;
			action = MapEntry.MapAction.ADD;//2
		}
		//For UPDATE action
		//Set PROD_PERM according to the input value
		//If the input PROD_PERM is less than zero set to default(3056)
		//Set Action to be UPDATE
		void performUpdateAction(int newpp) {
			if(newpp<=0)
				newpp = DEFAULT_PROD_PERM;
			prodPerm = newpp;
			action = MapEntry.MapAction.UPDATE;//1
		}
		//For DELETE action
		//Set Action to be DELETE
		void performDeleteAction() {
			action = MapEntry.MapAction.DELETE;//3
		}
	}

 

  • EMA C++

SymbolListMapEntry.h

#pragma once
#include <string>
#include <Ema.h>

//The class is to represent an entry in the map of Symbol List.
class SymbolListMapEntry {
public:
	inline int getAction() {
		return action;
	}
	inline int getProdPerm() {
		return prodPerm;
	}
	inline std::string getProSymb() {
		return provSymb;
	}
	inline static int getDefaultProdPerm() {
		return DEFAULT_PROD_PERM;
	}
	void performAddAction(int newpp, std::string newps);
	void performUpdateAction(int newpp);
	void performDeleteAction();

private:
	//Action can be UPDATE(1), ADD(2) or DELETE(3)
	 int action;
	//PROD_PERM  which field id is 1 and type is UINT64 
	 int prodPerm;
	//PROV_SYMB  which field id is 3422 and type is RMTES_STRING
	std::string provSymb;
	static const int DEFAULT_PROD_PERM = 3056;
};

SymbolListMapEntry.cpp

#include "SymbolListMapEntry.h"

using namespace thomsonreuters::ema::access;

//For ADD action
//Set PROD_PERM and PROV_SYMB according to the input values
//If the input PROD_PERM is less than zero set to default(3056)
//Set Action to be ADD
void SymbolListMapEntry::performAddAction(int newpp, std::string newps) {
	if (newpp <= 0)
		newpp = DEFAULT_PROD_PERM;
	prodPerm = newpp;
	provSymb = newps;
	action = MapEntry::MapAction::AddEnum;//2
}
//For UPDATE action
//Set PROD_PERM according to the input value
//If the input PROD_PERM is less than zero set to default(3056)
//Set Action to be UPDATE
void SymbolListMapEntry::performUpdateAction(int newpp) {
	if (newpp <= 0)
		newpp = DEFAULT_PROD_PERM;
	prodPerm = newpp;
	action = MapEntry::MapAction::UpdateEnum;//1
}
//For DELETE action
//Set Action to be DELETE
void SymbolListMapEntry::performDeleteAction() {
	action = MapEntry::MapAction::DeleteEnum;//3
}

 

  1. Create a main class named NiProvider in a new file named NiProvider.java/.h/.cpp. The class declares and initializes/uninitializes the variables used to create Symbol List map as shown below. 
  • EMA Java
public class NiProvider {
		//The maximum number of entries in the Symbol List
		int MAX_ENTRIES=150;
		//The minimum number of entries in the Symbol List
		int MIN_ENTRIES=20;
		//The maximum chance of the DELETE action is 10 
		//It the random number is 1-10, perform DELETE action 
		int deleteMaxChance = 10;
		//The maximum chance of the UPDATE action is 40 
		//It the random number is 11-40, perform UPDATE action 
		int updateMaxChance = 40; 
		//The maximum chance of the ADD action is 100
		//It the random number is 41-100, perform ADD action 
		int addMaxChance = 100; 
		//The List of SymbolListMapEntry represents all entries in the Symbol List from both Refresh and Update messages  
		List<SymbolListMapEntry> symbolListMap;
		//The List of SymbolListMapEntry represents the entries in an update message  
		List<SymbolListMapEntry> updateMap;
		//The Set keeps the PROV_SYMB/Key in the current Symbol List
		//It is used to verify if a new generated PROV_SYMB/Key is duplicated with any one in the Set
		HashSet<String> currentKeys;
		//The Random class is used to generate PROD_PERM and PROV_SYMB/Key at random
		//It is used to choose an Action, an entry which will be updated or deleted at random
		Random randomInt;
		//Initialize the NiProvider's variables
		public NiProvider() {
			symbolListMap = new ArrayList<SymbolListMapEntry>(MAX_ENTRIES);
			updateMap = new ArrayList<SymbolListMapEntry>(5);
			currentKeys = new HashSet<String>(MAX_ENTRIES);
			randomInt = new Random();
		}
   }

 

  • EMA C++

                       NiProvider.h

#pragma once
#include <vector>
#include <unordered_set>
#include "SymbolListMapEntry.h"
#include <time.h>

#ifdef WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif

#include "Ema.h"

void sleep(int millisecs)
{
#if defined WIN32
	::Sleep((DWORD)(millisecs));
#else
	struct timespec sleeptime;
	sleeptime.tv_sec = millisecs / 1000;
	sleeptime.tv_nsec = (millisecs % 1000) * 1000000;
	nanosleep(&sleeptime, 0);
#endif
}

class NiProvider {
public:
	//Initialize the NiProvider's variables
	NiProvider();
	//Uninitialize the NiProvider's variables
	~NiProvider();

	SymbolListMapEntry* generateNewEntry();
	void generateUpdatedEntries();
	
	//The List of SymbolListMapEntry represents the entries in an update message  
	std::vector<SymbolListMapEntry*>* updateMap;
private:
	//The maximum number of entries in the Symbol List
	static const int MAX_ENTRIES = 150;
	//The minimum number of entries in the Symbol List
	static const int MIN_ENTRIES = 20;
	//The maximum chance of the DELETE action is 10 
	//It the random number is 1-10, perform DELETE action 
	static const int deleteMaxChance = 10;
	//The maximum chance of the UPDATE action is 40 
	//It the random number is 11-40, perform UPDATE action 
	static const int updateMaxChance = 40;
	//The maximum chance of the ADD action is 100
	//It the random number is 41-100, perform ADD action 
	static const int addMaxChance = 100;
	//The List of SymbolListMapEntry represents all entries in the Symbol List from both Refresh and Update messages  
	std::vector<SymbolListMapEntry*>* symbolListMap;
	//The Set keeps the PROV_SYMB/Key in the current Symbol List
	//It is used to verify if a new generated PROV_SYMB/Key is duplicated with any one in the Set
	std::unordered_set<std::string>* currentKeys;
};

NiProvider.cpp

#include "NiProvider.h"
#include <iostream>
#include <cstring>


using namespace thomsonreuters::ema::access;
using namespace thomsonreuters::ema::rdm;
using namespace std;

NiProvider::NiProvider() {
	symbolListMap = new std::vector<SymbolListMapEntry*>();
	updateMap = new std::vector<SymbolListMapEntry*>();
	currentKeys = new std::unordered_set<std::string>();
}

NiProvider::~NiProvider() {
	for (vector<SymbolListMapEntry*>::iterator it = symbolListMap->begin(); it != symbolListMap->end(); it++)
	{
		delete *it;
	}
	delete symbolListMap;
	delete updateMap;
	delete currentKeys;
}

 

  1. Create a utility method in NiProvider class named generateNewEntry(). The method creates a new entry which is SymbolListMapEntry instance. The entry contains a random symbol, some fields and ADD action. The entry is added to the Symbol List map later.
  • EMA Java
//Generate a new SymbolListMapEntry which represents an entry.
   public SymbolListMapEntry generateNewEntry()
   {
        //Initialize a new SymbolListMapEntry
	    SymbolListMapEntry newEntry = new SymbolListMapEntry();
	    //The variable to keep PROV_SYMB/Key
	    String tmpKey;
	    //PROV_SYMB pattern is [A-Z][A-Z][A-Z].[A-Z]
        //Generate a PROV_SYMB/Key till it is not the same as any current PROV_SYMB/Key 
	    do {
            tmpKey = String.valueOf(new char[] { (char)(randomInt.nextInt(26) + 65), (char)(randomInt.nextInt(26) + 65), (char)(randomInt.nextInt(26) + 65), '.',(char)(randomInt.nextInt(26) + 65) });
		} while(currentKeys.contains(tmpKey));
	    //Set the SymbolListMapEntry with ADD Action 
	    newEntry.performAddAction(SymbolListMapEntry.getDefaultProdPerm(),tmpKey); 
	    //Add the SymbolListMapEntry in the Symbol List
	    symbolListMap.add(newEntry);
	    //Add the PROV_SYMB/Key of the SymbolListMapEntry in the PROV_SYMB/Key Set
	    currentKeys.add(newEntry.getProSymb());
	    return newEntry;
	}

 

  • EMA C++

                       NiProvider.h

#include <time.h>
   class NiProvider {
   public:

        SymbolListMapEntry* generateNewEntry();

NiProvider.cpp

//Generate a new SymbolListMapEntry which represents an entry.
   SymbolListMapEntry* NiProvider::generateNewEntry()
   {
	    //Initialize a new SymbolListMapEntry
	    SymbolListMapEntry* newEntry = new SymbolListMapEntry();
	    //The variable to keep PROV_SYMB/Key
	    std::string tmpKey;
	    srand(time(NULL));
	    //PROV_SYMB pattern is [A-Z][A-Z][A-Z].[A-Z]
	    //Generate a PROV_SYMB/Key till it is not the same as any current PROV_SYMB/Key 
	    do {
		    tmpKey.clear();
		    tmpKey += (char)(rand() % 26 + 65);
		    tmpKey += (char)(rand() % 26 + 65);
		    tmpKey += (char)(rand() % 26 + 65);
		    tmpKey += '.';
		    tmpKey += (char)(rand() % 26 + 65);
	    } while (currentKeys->count(tmpKey));
	    //Set the SymbolListMapEntry with ADD Action 
	    newEntry->performAddAction(SymbolListMapEntry::getDefaultProdPerm(), tmpKey);
	    //Add the SymbolListMapEntry in the Symbol List
	    symbolListMap->push_back(newEntry);
	    //Add the PROV_SYMB/Key of the SymbolListMapEntry in the PROV_SYMB/Key Set
	    currentKeys->insert(newEntry->getProSymb());
	    return newEntry;
   }

 

  1. Create a utility method in NiProvider class named generateUpdatedEntries(). The method creates a Symbol List Map which will be added to the payload of an Update message. The map contains random entries which have random actions i.e. ADD, UPDATE or DELETE.
  • EMA Java
    //Generate the List of new entries which will be added to the next update message
    public void generateUpdatedEntries()
    {
        //Clear the list of entries which have been added to the latest update message
		updateMap.clear();
		//Create the PROV_SYMB/Key Set of the update message
	    HashSet<String> updateEntries = new HashSet<String>();
	    //Generate a new ProdPerm which is 1000-5000 at random  
	   	int newProdPerm = randomInt.nextInt(4000) + 1000;
	    //The list contains maximum 5 entries, for each one do:
	    for (int i = 0; i < 5; i++) 
	    {
	        //Declare SymbolListMapEntry to keep a new entry
	    	SymbolListMapEntry updateEntry = null;
	    	//Select an Action at random
	        int action = randomInt.nextInt(100);
            //If the number of entries in the Symbol List is less than the minimum(20)
	        //or the random Action is ADD
	        if (symbolListMap.size() < MIN_ENTRIES || action > updateMaxChance) 
	        {
                //If the number of entries in the Symbol List is less than maximum(150)
	        	if(symbolListMap.size() < MAX_ENTRIES) {
	        	//Perform ADD action by generating a new entry with ADD action
	        		updateEntry = generateNewEntry();
	            }
                //If the number of entries in the Symbol List equal or is more than the maximum(150),
	            //try to delete an existing entry in the Symbol List
	            else 
	            {   
	        		//Select the deleted entry in the Symbol List at random
	            	int index = randomInt.nextInt(symbolListMap.size());
	            	updateEntry = symbolListMap.get(index);
                    //If the PROV_SYMB/Key of the selected entry is duplicated with 
                    //any PROV_SYMB/Key in the update Set, skip to create the next entry
	            	if (updateEntries.contains(updateEntry.getProSymb()))
	            	{
	            	    continue;
	            	}
	            	//Otherwise, perform DELETE action on the selected entry
	            	updateEntry.performDeleteAction();
	            	//Remove the deleted entry from the Symbol List
	            	symbolListMap.remove(updateEntry);
                    //Remove the PROV_SYMB/Key of the deleted entry from the PROV_SYMB/Key Set
	            	currentKeys.remove(updateEntry.getProSymb());
	            }
	        }
	        //If the random action is UPDATE
	        else if (action > deleteMaxChance) 
	        {
	        	//Select the updated entry in the Symbol List at random
	            int index = randomInt.nextInt(symbolListMap.size());
	            updateEntry = symbolListMap.get(index);
	            //If the PROV_SYMB/Key of the selected entry is duplicated with 
            	//any PROV_SYMB/Key in the update Set, skip to create the next entry
	            if (updateEntries.contains(updateEntry.getProSymb()))
		        {
	            		continue;
		        }
	            //Otherwise, perform UPDATE action on the selected entry  
	            updateEntry.performUpdateAction(newProdPerm);
	        }
	        //If the random action is DELETE
	        else
	        {
                //If the number of entries in the Symbol List equal or less than minimum(20)
	            if (symbolListMap.size() <= MIN_ENTRIES) {
	            	//Perform ADD action by generating a new entry with ADD action
	            	updateEntry = generateNewEntry();
	            } 
	            else 
                {   //If the number of entries in the Symbol List is more than   the minimum(20),
	    	        //try to delete an existing entry in the Symbol List
	            	//Select the deleted entry in the Symbol List at random
		            int index = randomInt.nextInt(symbolListMap.size());
		            updateEntry = symbolListMap.get(index);
                    //If the PROV_SYMB/Key of the selected entry is duplicated with 
                    //any PROV_SYMB/Key in the update Set, skip to create the next entry
		            if (updateEntries.contains(updateEntry.getProSymb()))
		            {
		            		continue;
		            }
		            //Otherwise, perform DELETE action on the selected entry
		            updateEntry.performDeleteAction();
		            //Remove the deleted entry from the Symbol List
		            symbolListMap.remove(updateEntry);
                    //Remove the PROV_SYMB/Key of the deleted entry from the PROV_SYMB/Key Set
		            currentKeys.remove(updateEntry.getProSymb());
	            }
	        }
	        //Add the PROV_SYMB/Key in the Set of the new update message.
	        updateEntries.add(updateEntry.getProSymb());
	        //Add the entry in the List of the new update message.
	        updateMap.add(updateEntry);
	    }
    }

 

  • EMA C++

                       NiProvider.h

   class NiProvider {
   public:
        void generateUpdatedEntries(); 

 

                       NiProvider.cpp

   //Generate the List of new entries which will be added to the next update message
   void NiProvider::generateUpdatedEntries()
   {
        //Create the PROV_SYMB/Key Set of the update message
	    std::unordered_set<std::string>* updateEntries = new std::unordered_set<std::string>();
	    //Generate a new ProdPerm which is 1000-5000 at random  
	    int newProdPerm = rand() % 4000 + 1000;
	    //The list contains maximum 5 entries, for each one do:
	    for (int i = 0; i < 5; i++)
	    {
		    //Declare SymbolListMapEntry to keep a new entry
		    SymbolListMapEntry* updateEntry = 0;
		    //Select an Action at random
		    int action = rand() % 100;
		    //If the number of entries in the Symbol List is less than the minimum(20)
		    //or the random Action is ADD
		    if (symbolListMap->size() < MIN_ENTRIES || action > updateMaxChance)
		    {
			    //If the number of entries in the Symbol List is less than maximum(150)
			    if (symbolListMap->size() < MAX_ENTRIES) {
				    //Perform ADD action by generating a new entry with ADD action
				    updateEntry = generateNewEntry();
			    }
			    //If the number of entries in the Symbol List equal or is more than the maximum(150),
			    //try to delete an existing entry in the Symbol List
			    else
			    {
				    //Select the deleted entry in the Symbol List at random
				    int index = rand() % symbolListMap->size();
				    updateEntry = (*symbolListMap)[index];
				    //If the PROV_SYMB/Key of the selected entry is duplicated with 
				    //any PROV_SYMB/Key in the update Set, skip to create the next entry
				    if (updateEntries->count(updateEntry->getProSymb()))
				    {
					    continue;
				    }
				    //Otherwise, perform DELETE action on the selected entry
				    updateEntry->performDeleteAction();
				    //Remove the deleted entry from the Symbol List
				    symbolListMap->erase(symbolListMap->begin() + index);
				    //Remove the PROV_SYMB/Key of the deleted entry from the PROV_SYMB/Key Set
				    currentKeys->erase(updateEntry->getProSymb());
			    }
		    }
		    //If the random action is UPDATE
		    else if (action > deleteMaxChance)
		    {
			    //Select the updated entry in the Symbol List at random
			    int index = rand() % symbolListMap->size();
			    updateEntry = (*symbolListMap)[index];
			    //If the PROV_SYMB/Key of the selected entry is duplicated with 
			    //any PROV_SYMB/Key in the update Set, skip to create the next entry
			    if (updateEntries->count(updateEntry->getProSymb()))
			    {
				    continue;
			    }
			    //Otherwise, perform UPDATE action on the selected entry  
			    updateEntry->performUpdateAction(newProdPerm);
		    }
		    //If the random action is DELETE
		    else
		    {
			    //If the number of entries in the Symbol List equal or less than minimum(20)
			    if (symbolListMap->size() <= MIN_ENTRIES) {
				    //Perform ADD action by generating a new entry with ADD action
				    updateEntry = generateNewEntry();
			    }
			    else
			    {   //If the number of entries in the Symbol List is more than   the minimum(20),
				    //try to delete an existing entry in the Symbol List
				    //Select the deleted entry in the Symbol List at random
				    int index = rand() % symbolListMap->size();
				    updateEntry = (*symbolListMap)[index];
				    //If the PROV_SYMB/Key of the selected entry is duplicated with 
				    //any PROV_SYMB/Key in the update Set, skip to create the next entry
				    if (updateEntries->count(updateEntry->getProSymb()))
				    {
					    continue;
				    }
				    //Otherwise, perform DELETE action on the selected entry
				    updateEntry->performDeleteAction();
				    //Remove the deleted entry from the Symbol List
				    symbolListMap->erase(symbolListMap->begin() + index);
				    //Remove the PROV_SYMB/Key of the deleted entry from the PROV_SYMB/Key Set
				    currentKeys->erase(updateEntry->getProSymb());
			    }
		    }
		    //Add the PROV_SYMB/Key in the Set of the new update message.
		    updateEntries->insert(updateEntry->getProSymb());
		    //Add the entry in the List of the new update message.
		    updateMap->push_back(updateEntry);
	    }
   }

 

  1. Add the main function in NiProvider class to start EMA Non-Interactive Provider and publish Symbol List as explained below:
  • Create and run EMA Non-Interactive Provider, OmmProvider class, which connects and logs in to ADH.
  • Create a Symbol List Map using generateNewEntry() created in the 3rd step. Then, put the map to the payload of a Refresh message and publish the message to ADH using EMA method named OmmProvider.submit(..)
  • Create a Symbol List map for an Update message using generateUpdatedEntries() created in the 4th step. Then, put the map into a payload of an Update message and publish the message to ADH using EMA method named OmmProvider.submit(..).  
  • Send one Update message every 3 seconds till 20 Update messages then log out from ADH and shuts down. 

The complete source code of the main function is shown below:

  • EMA Java
public static void main(String[] args)
{
	    OmmProvider provider = null;
		try
		{
            //Use the default NiProvider's configuration in EmaConfig.xml found in the working directory
            OmmNiProviderConfig config = EmaFactory.createOmmNiProviderConfig();
            //change "adhserver:14003" to your ADH server IP/name and its port
			//change "user" to NI Provider's user which logs in to ADH
            provider = EmaFactory.createOmmProvider(config.host("adhserver:14003").username("user"));
			System.out.println("Non-Interactive Provider starts.");
            //The handle which identifies item stream on which to send the Refresh message and Update messages of a Symbol List.
			long itemHandle = 5;
            //Create an object of NiProvider which provides the methods to create/update and manage Symbol List.
			NiProvider niprov = new NiProvider();			
            //Create a map which is encoded content of Symbol List. The map contains entries.
			Map map = EmaFactory.createMap();
			//Create a field list which is encoded content of an entries.
			FieldList fieldList = EmaFactory.createFieldList();
			//Encode a map contains 25 entries
			for( int i = 0; i < 25; i++ )
			{
                //generate SymbolListMapEntry instance consisting of action(ADD), PROD_PERM field and PROV_SYMB field
				SymbolListMapEntry newEntry = niprov.generateNewEntry();
				//set PROD_PERM field
			    fieldList.add(EmaFactory.createFieldEntry().uintValue(1, newEntry.getProdPerm()));
				//set PROV_SYMB field
				fieldList.add(EmaFactory.createFieldEntry().rmtes(3422, ByteBuffer.wrap(newEntry.getProSymb().getBytes())));
                //set PROV_SYMB as a Key, set Action and fields to an entry. Then, add the entry to the map.
                map.add(EmaFactory.createMapEntry().keyAscii(newEntry.getProSymb(), newEntry.getAction(), fieldList));
				fieldList.clear();
			}
			System.out.println("Non-Interactive Provider is publishing a Symbol List Refresh message.");
            //Publish a single complete Refresh message of the Symbol List named 0#NEWSBL to the service named NI_PUB. 
            //The payload of the Refresh message is the map created previously.
			provider.submit( EmaFactory.createRefreshMsg().domainType(EmaRdm.MMT_SYMBOL_LIST).serviceName("NI_PUB").name("0#NEWSBL").state(OmmState.StreamState.OPEN, OmmState.DataState.OK, OmmState.StatusCode.NONE, "UnSolicited Refresh Completed").payload(map).complete(true), itemHandle);
            Thread.sleep(1000);
			map.clear();
			System.out.println("Non-Interactive Provider starts publishing Symbol List Update messages.");
			//Send one Update message every 3 seconds till 20 Update messages
			for( int i = 0; i < 20; i++ ) 
            {
				//Generate the List of new entries
				niprov.generateUpdatedEntries();
				Iterator<SymbolListMapEntry> it = niprov.updateMap.iterator();
				//for each entry in the List
				while(it.hasNext()) 
				{
					SymbolListMapEntry anEntry = it.next();
					//An entry which action is ADD or UPDATE contains PROD_PERM and PROV_SYMB field. 
                    if (anEntry.getAction()!=MapEntry.MapAction.DELETE) 
                    {
					    //set PROD_PERM field
                        fieldList.add(EmaFactory.createFieldEntry().uintValue(1, anEntry.getProdPerm()));
						//set PROV_SYMB field
                        fieldList.add(EmaFactory.createFieldEntry().rmtes(3422, ByteBuffer.wrap(anEntry.getProSymb().getBytes())));
					}
		            //set PROV_SYMB as a Key, set Action and fields to an entry. Then, add the entry to the map.
					map.add(EmaFactory.createMapEntry().keyAscii(anEntry.getProSymb(),anEntry.getAction(),fieldList));
					fieldList.clear();
				}
                //Publish an Update message of the Symbol List named 0#NEWSBL to the service named NI_PUB. 
                //The payload of the Update message is the map created previously.
				provider.submit( EmaFactory.createUpdateMsg().serviceName("NI_PUB").name("0#NEWSBL").domainType(EmaRdm.MMT_SYMBOL_LIST).payload( map ), itemHandle );
				map.clear();
				Thread.sleep(3000);
			}
			System.out.println("Non-Interactive Provider published all Symbol List messages. It exits.");
		} 
		catch (InterruptedException | OmmException excp)
		{
			System.out.println(excp.getMessage());
		}
		finally 
		{
			if (provider != null) provider.uninitialize();
		}
}

 

  • EMA C++

                       NiProvider.cpp

   int main(int argc, char* argv[])
   {
        try
	    {
		    //Use the default NiProvider's configuration in EmaConfig.xml found in the working directory
		    //change "adhserver:14003" to your ADH server IP/name and its port
		    //change "user" to NI Provider's user which logs in to ADH
		    OmmProvider provider(OmmNiProviderConfig().host("192.168.27.46:14003").username("user"));


		    cout<< "Non-Interactive Provider starts." << endl;
		    //The handle which identifies item stream on which to send the Refresh message and Update messages of a Symbol List.
		    long itemHandle = 5;
		    //Create an object of NiProvider which provides the methods to create/update and manage Symbol List.
		    NiProvider* niprov = new NiProvider();
		    //Create a map which is encoded content of Symbol List. The map contains entries.
		    Map map;
		    //Create a field list which is encoded content of an entries.
		    FieldList fieldList;
		    //Encode a map contains 25 entries
		    for (int i = 0; i < 25; i++)
		    {
			    //generate SymbolListMapEntry instance consisting of action(ADD), PROD_PERM field and PROV_SYMB field
			    SymbolListMapEntry* newEntry = niprov->generateNewEntry();
			    string prodSymb = newEntry->getProSymb();
			    //set PROD_PERM field
			    fieldList.addUInt(1, newEntry->getProdPerm());
			    //set PROV_SYMB field
			    char *cstr = new char[prodSymb.length() + 1];
			    strcpy(cstr, prodSymb.c_str());
			    fieldList.addRmtes(3422, EmaBuffer(cstr, sizeof(cstr)));
			    fieldList.complete();

			    //set PROV_SYMB as a Key, set Action and fields to an entry. Then, add the entry to the map.
			    map.addKeyAscii(EmaString(cstr), static_cast<MapEntry::MapAction>(newEntry->getAction()), fieldList);
			    delete[] cstr;
			    fieldList.clear();
		    }
		    map.complete();

		    cout<<"Non-Interactive Provider is publishing a Symbol List Refresh message."<<endl;
		    //Publish a single complete Refresh message of the Symbol List named 0#NEWSBL to the service named NI_PUB. 
		    //The payload of the Refresh message is the map created previously.
		    provider.submit(RefreshMsg().domainType(MMT_SYMBOL_LIST).serviceName("API_BRICKBAR_NI").name("0#NEWSBL").state(OmmState::StreamState::OpenEnum, OmmState::DataState::OkEnum, OmmState::StatusCode::NoneEnum, "UnSolicited Refresh Completed").payload(map).complete(true), itemHandle);
		    Sleep(1000);
		    map.clear();
		    cout<< "Non-Interactive Provider starts publishing Symbol List Update messages." << endl;
		    //Send one Update message every 3 seconds till 20 Update messages
		    for (int i = 0; i < 20; i++)
		    {
			    //Generate the List of new entries
			    niprov->generateUpdatedEntries();
			
			    for (vector<SymbolListMapEntry*>::iterator it = niprov->updateMap->begin(); it != niprov->updateMap->end();it++)
			    {
				    SymbolListMapEntry* anEntry = *it;
				    //An entry which action is ADD or UPDATE contains PROD_PERM and PROV_SYMB field. 
				    string prodSymb = anEntry->getProSymb();
				    char *cstr = new char[prodSymb.length() + 1];
				    strcpy(cstr, prodSymb.c_str());

				    if (anEntry->getAction() != MapEntry::MapAction::DeleteEnum)
				    {
					    //set PROD_PERM field
					    fieldList.addUInt(1, anEntry->getProdPerm());
					    //set PROV_SYMB field
					    fieldList.addRmtes(3422, EmaBuffer(cstr, sizeof(cstr)));
				    }
				    fieldList.complete();

				    //set PROV_SYMB as a Key, set Action and fields to an entry. Then, add the entry to the map.
				    map.addKeyAscii(EmaString(cstr), static_cast<MapEntry::MapAction>(anEntry->getAction()), fieldList);
				    delete[] cstr;
				    fieldList.clear();
			    }
			    map.complete();
			    //Publish an Update message of the Symbol List named 0#NEWSBL to the service named NI_PUB. 
			    //The payload of the Update message is the map created previously.
			    provider.submit(UpdateMsg().serviceName("API_BRICKBAR_NI").name("0#NEWSBL").domainType(MMT_SYMBOL_LIST).payload(map), itemHandle);
			    map.clear();

			    //Clear the list of entries which have been added to the latest update message
			    for (int i = 0; i < niprov->updateMap->size(); i++)
			    {
				    if ((*niprov->updateMap)[i]->getAction() == (int)MapEntry::DeleteEnum)
					    delete (*niprov->updateMap)[i];
			    }
			    niprov->updateMap->clear();

			    sleep(3000);
		    }
		    cout << "Non-Interactive Provider published all Symbol List messages. It exits." << endl;
	    }
	    catch (const OmmException& excp)
	    {
		    cout << excp << endl;
	    }
	    return 0;
   }

 

You can download the complete Java and C++ application source code explained above at TR-API-Samples/Article.EMA.JavaCpp.SymbolListNIP GitHub

Build and Run the application

EMA Java

EMA, ETA, and EMA's dependency library files are required to build and run the EMA Java NIP application. Furtunately, all files are shipped with the Elektron-SDK Java package. Hence, you just set the CLASSPATH to all of them before building and running the application.

The following example command lines show how to set the CLASSPATH, build and run the application on Windows: 

  1. Set the classpath to EMA, ETA, and EMA's dependency library files. For example:
set CLASSPATH=C:\Elektron-SDK\Java\Ema\Libs\ema-3.2.0.1.jar;C:\Elektron-SDK\Java\Eta\Libs\upa-3.2.0.1.jar;C:\Elektron-SDK\Java\Eta\Libs\upaValueAdd-3.2.0.1.jar;C:\Elektron-SDK\Elektron-SDK-BinaryPack\Java\Ema\Libs\apache\commons-collections-3.2.2.jar;C:\Elektron-SDK\Elektron-SDK-BinaryPack\Java\Ema\Libs\apache\commons-configuration-1.10.jar;C:\Elektron-SDK\Elektron-SDK-BinaryPack\Java\Ema\Libs\apache\commons-lang-2.6.jar;C:\Elektron-SDK\Elektron-SDK-BinaryPack\Java\Ema\Libs\apache\commons-logging-1.2.jar;C:\Elektron-SDK\Elektron-SDK-BinaryPack\Java\Ema\Libs\SLF4J\slf4j-1.7.12\slf4j-api-1.7.12.jar;C:\Elektron-SDK\Elektron-SDK-BinaryPack\Java\Ema\Libs\SLF4J\slf4j-1.7.12\slf4j-jdk14-1.7.12.jar;C:\Elektron-SDK\Elektron-SDK-BinaryPack\Java\Ema\Libs\xpp3-1.1.4c.jar

 

  1. Compile the application:
javac com\thomsonreuters\ema\examples\training\niprovider\example__SymbolList__Streaming\*.java

 

  1. Run the application. Before you run it, include the path containing NIP application class files generated from the step 2nd to the CLASSPATH.
set CLASSPATH=.;%CLASSPATH%

Then, you can run the application using the following commandline:

java com.thomsonreuters.ema.examples.training.niprovider.example__SymbolList__Streaming.NiProvider

 

EMA C++

EMA library (i.e. libema) and dependency libraries (i.e. librssl, librsslVA) are required to build and run the EMA C++ NIP application. The library is provided in the Elektron-SDK C++ package. Hence, you can just set the project or makefile to load the library. Please note that to build Windows application with EMA static library, the “/D __EMA_STATIC_BUILD__” option is required.

In Linux package, EMA shared libraries are provided with its package version appended to the end. For example, libema.so.3.1. The API provides a helpful script that will create soft links for the appropriate library names. You need to run the script (i.e. LinuxSoLink) in the package before running the EMA NIP application. For more information, please see the “2.3.1 Shared Libraries” section of README file in the EMA Linux package

Anyway, you can refer to the Visual Studio project provided with the example source code. To use the Visual Studio project, the "EMAInstall" variable needs to be added in Windows Environment variable to point to EMA installation path.

For dynamic library, you will need to set PATH or LD_LIBRARY_PATH before running the application.

   Ema\Libs\<OS>\<Build Option>\Shared
   Eta\Libs\<OS>\<Build Option>\Shared

You can run the application using the command line:

NiProvider.exe

 

The Example Result

When you run the NIP application publishing Symbol List, you should see the messages like below:

 

    May 31, 2018 4:22:44 PM com.thomsonreuters.ema.access.ChannelCallbackClient reactorChannelEventCallback
    INFO: loggerMsg
        ClientName: ChannelCallbackClient
        Severity: Info
        Text:    Received ChannelUp event on channel Channel_3
        Instance Name Provider_1_1
        Component Version adh3.0.6.L1.solaris.tis.rrg 64-bit
    loggerMsgEnd


    Non-Interactive Provider starts.
    Non-Interactive Provider is publishing a Symbol List Refresh message.
    Non-Interactive Provider starts publishing Symbol List Update messages.
    Non-Interactive Provider published all Symbol List messages. It exits.

 

While the NIP application is running, you can run the EMA Java Consumer named example270__SymbolList or EMA C++ Consumer named 270__SymbolList to verify the NIP's service and its published Symbol List. The Consumer application must connect to ADS(one component of TREP) connecting to ADH which the NIP publishes Symbol List. According to the given source code, the Consumer application needs to request to the service named NI_PUB with the Symbol list named 0#NEWSBL

The example result shown in example270__SymbolList:

    May 31, 2018 4:55:28 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 ads3.0.6.L1.solaris.tis.rrg 64-bit
    loggerMsgEnd


    Item Name: 0#NEWSBL
    Service Name: NI_PUB
    Item State: Open / Ok / None / 'UnSolicited Refresh Completed'
    Name	Action

    MVO.T	Add
        
    PROD_PERM	
    3056
    PROV_SYMB	
    MVO.T

    BRT.S	Add
        
    PROD_PERM	
    3056
    PROV_SYMB	
    BRT.S

    OPB.P	Add
        
    PROD_PERM	
    3056
    PROV_SYMB	
    OPB.P

    EVK.A	Add
        
    PROD_PERM	
    3056
    PROV_SYMB	
    EVK.A

    GMV.A	Add
        
    PROD_PERM	
    3056
    PROV_SYMB	
    GMV.A

 

Trobleshooting

Q: When compiling the NIP EMA Java application, it shows "error: package [package name] does not exist"

com\thomsonreuters\ema\examples\training\niprovider\example__SymbolList__Streaming\NiProvider.java:10: error: package com.thomsonreuters.ema.access does not exist import com.thomsonreuters.ema.access.EmaFactory;

A: the CLASSPATH is not set or set to the wrong path. See step 1 in EMA Java under the section Build and Run the application.

 

Q: When running the NIP EMA Java application, it shows "Error: Could not find or load main class [class]"

Error: Could not find or load main class com.thomsonreuters.ema.examples.training.niprovider.example__SymbolList__Streaming.NiProvider

A: the path containing NIP EMA Java application class files are not included in the CLASSPATH or set to the wrong path or the run [class] may be wrong. See step 3 in EMA Java under the section Build and Run the application.

 

Q: When running the application, it shows "Error - exceeded initialization timeout (5 s)" and "login failed (timed out after waiting 45000 milliseconds) for [server]:[port])"

    Jun 08, 2018 9:38:25 AM com.thomsonreuters.ema.access.ChannelCallbackClient reactorChannelEventCallback
    WARNING: loggerMsg
        ClientName: ChannelCallbackClient
        Severity: Warning
        Text:    Received ChannelDownReconnecting event on channel Channel_3
            RsslReactor Channel is null
            Error Id 0
            Internal sysError 0
            Error Location Reactor.processWorkerEvent
            Error text Error - exceeded initialization timeout (5 s)
    loggerMsgEnd


    Jun 08, 2018 9:38:33 AM com.thomsonreuters.ema.access.OmmBaseImpl handleLoginReqTimeout
    SEVERE: loggerMsg
        ClientName: Provider_1_1
        Severity: Error
        Text:    login failed (timed out after waiting 45000 milliseconds) for 192.168.27.99:14003)
    loggerMsgEnd


    login failed (timed out after waiting 45000 milliseconds) for 192.168.27.99:14003)

A: the possible causes of the problem are:

  • The host(it does not exist in the network) or port(it is the port of another process) set in the application source code is wrong or the host running ADH goes down. Please contact TREP administrator to verify the correct host/IP of ADH and if the host goes up. 
  • There is the network or firewall problem that cause the NIP application cannot connect to the server. Please contact your network administrator to verify this.

Q: When running the application, it shows "Error initializing channel: errorId=-1 text=Connection refused: no further information" and "login failed (timed out after waiting 45000 milliseconds) for [host]:[port])"

    Jun 08, 2018 9:47:22 AM com.thomsonreuters.ema.access.ChannelCallbackClient reactorChannelEventCallback
    WARNING: loggerMsg
        ClientName: ChannelCallbackClient
        Severity: Warning
        Text:    Received ChannelDownReconnecting event on channel Channel_3
            RsslReactor Channel is null
            Error Id 0
            Internal sysError 0
            Error Location Reactor.processWorkerEvent
            Error text Error initializing channel: errorId=-1 text=Connection refused: no further information
    loggerMsgEnd


    Jun 08, 2018 9:47:26 AM com.thomsonreuters.ema.access.OmmBaseImpl handleLoginReqTimeout
    SEVERE: loggerMsg
        ClientName: Provider_1_1
        Severity: Error
        Text:    login failed (timed out after waiting 45000 milliseconds) for 192.168.27.11:14003)
    loggerMsgEnd


    login failed (timed out after waiting 45000 milliseconds) for 192.168.27.11:14003)

A: the possible causes of the problem are:

  • ADH goes down, the host(it does not have ADH) or port(it is closed) set in the application source code is wrong. Please contact TREP administrator to verify the correct host/IP of ADH and if ADH goes up. 
  • The firewall blocks the messages between the NIP application and ADH. Please contact your network administrator to verify and fix this.

Q: When running the application, it shows "Login rejected. Non-consumer attempting login."

    Jun 08, 2018 9:50:52 AM com.thomsonreuters.ema.access.LoginCallbackClient rdmLoginMsgCallback
    SEVERE: loggerMsg
        ClientName: LoginCallbackClient
        Severity: Error
        Text:    RDMLogin stream was closed with status message
            username user
            usernameType 1
	
	        State: Closed/Suspect/Usage error - text: "Login rejected. Non-consumer attempting login."
    loggerMsgEnd

A: The application connects to ADS(the default port is 14002) instead of ADH(the default port is 14003). ADS is for consumer applications not NIP applications. Please change the port in the source code to be 14003. If the NIP application still cannot connect to ADH, please contact TREP administrator who can provide you with the host/port of ADH.

 

Q: NIP application can connect and publish to ADH, but the consumer application does not receive data and shows "Service name of [service_name] is not found."

    Item Name: 0#NEWSBL
    Service Name: NI_PUB
    Item State: Closed / Suspect / None / 'Service name of 'NI_PUB' is not found.'

A: It means the service provided by NIP application(in the example application is NI_PUB) does not match with the service defined in the ADH configurations. Please contact TREP administrator who can help you to verify this and configure TREP for your NIP service.

Summary

After finishing this article, you will understand more about Symbol List, NIP application and how to develop an NIP application using EMA to publish symbol list. Also, you can modify this application to serve your requirements using EMA easier and faster including solving the common problems. If you do not have TREP(ADH and ADS), please contact Thomson Reuters Account team for the process and details. If you/your TREP administrator requires any TREP(ADH and ADS) assistance, You can contact the TREP support team directly by submitting your query to Contact Us New

References



For further details, please check out the following resources: