Example 1 - Call up exchange rates

Description of the scenario

This example describes how you can retrieve exchange rates with a REST call action and process them further in Intrexx.

The retrieval of exchange rates is triggered by clicking on the "Get rates" button in the "Exchange rates" application. The rates are then displayed. Each time you click again, the exchange rates are retrieved again. This is done for the US dollar (USD), British pound (GBP), Japanese yen (JPY) and Chinese renminbi yuan (CNY) as an example.

The exchange rates are written to a data group after each click. The data group contains one column per currency, each of which contains the current exchange rate.

The application can then make the requested rates available to other applications in the portal, for example for invoicing.

German customs

In the example, the rates are provided by German Customs via a REST API. They publish exchange rates daily that are used as a basis for the valuation of goods.

The URL of the page is: www.zoll.de/SiteGlobals/Functions/Kurse/App/KursExport.txt?view=jsonexportkurseZOLLWeb

Description of the response data

The data is provided as Json. They have the following form:

{
   "kurse":[
      {
         "kurswert":50.96635,
         "iso3":"EGP",
         "name":"Ägyptisches Pfund"
      },
      .....
      {
         "kurswert":10.8847,
         "iso3":"MAD",
         "name":"Marokkanischer Dirham"
      },
      .....
      {
         "kurswert":1.0638,
         "iso3":"USD",
         "name":"US-Dollar"
      }
   ]
}

The Json object contains an array with the name "kurse", in which a Json object is embedded for each currency. Each of these objects contains the following three fields:

  1. "kurswert" is the exchange rate

  2. "iso3" is the ISO designation of the currency

  3. "name" is the full name of the currency

Processing response data

In principle, a REST call makes all received data available in the processing context (SharedState) of a process (workflow). However, the respective REST APIs deliver the data differently, meaning that Intrexx cannot usually process the data automatically. This means that the data received must first be prepared using a Groovy action before they can be processed further.

Create application and process

The following section describes how to create the application and the process with a REST call action. The main steps are described and illustrated using screenshots.

Application structure

To be able to save the rates, the application requires a data group in which four columns are created for the four currencies. These columns are created with floating point numbers so that the incoming decimal places can be stored correctly.

The display is an overview page with a view table in which the four columns plus the timestamp of the last change are displayed. Since the process to be created is to update the data in the table, there must already be an entry to start with, which can then be updated. To do this, you can use the Page Wizard to create a simple entry page for all four currencies and then create an entry with any values. These will then simply be overwritten with the first REST call.

Structure of the process

Trigger workflow ()

The workflow is to be triggered by clicking on the "Get rates" button in the application. A generic event handler can be used for this purpose, which uses a UserWorkflowEventHandler as the event handler. This handler can be notified from the portal with the Javascript command triggerUserWorkflowEvent() and thus trigger the process (see below). When the handler is entered, a GUID is generated that is required later to notify the handler.

Set up the REST call ()

General The event handler now triggers a REST call that retrieves the exchange rates. In the example, the call "getExchangeRates" is called. The results can be accessed later in the processing context under this alias. Since "zoll.de" delivers a Json, the "Parse JSON" checkbox must be enabled. This converts the delivered text into a Json object that can then be processed in the further course of the workflow.

Authentication and header Since the page does not require any special authentication, no further settings are necessary for "Authentication and header".

Request A GET request is required to retrieve the Json. This goes to the host zoll.de and there to the path SiteGlobals/Functions/Kurse/App/KursExport.txt (the part of the address up to the "?", which indicates the start of the query parameters). The output as Json is controlled by setting the query parameter "view" to "jsonexportkurseZOLLWeb" is set to This can be entered directly in the list of query parameters.

Body As no data needs to be passed for the call, no payload is required on the "Body" tab.

Extract exchange rates ()

This workflow step involves processing the json received and placing selected exchange rates into the processing context. A Groovy script action is used for this. The exchange rates are extracted using the "JsonPath library".

The call stores the received json with the exchange rates in the processing context under its alias as getExchangeRates.body.json (the raw text is available under getExchangeRates.body.text is available). To extract the individual values, you can use the JsonPath library, for example, which is used to access previously placed REST calls. It contains an object JsonPath, which is made available via the line import com.jayway.jsonpath.JsonPath; is made available.

The structure of a Json path is merely outlined here. A detailed introduction can be found on the library page at https://github.com/json-path/JsonPath or tutorials like https://www.baeldung.com/guide-to-jayway-jsonpath. There are also pages on the Internet that evaluate JsonPaths and check whether they are correct (e.g. https://jsonpath.com/).

A Json path consists of a path through the hierarchically structured Json object. The symbol "$" stands for the entire json at the beginning, the individual steps are given the name of the respective element and are separated by a dot. In arrays, one or more elements can be selected using square brackets. JsonPaths also offer the option of checking predicates using "?()" and thus selecting elements that meet certain conditions. The elements are then designated with "@" in the query in order to access their subordinate elements.

In our case, to select a rate from the json ($), we need the array "kurse" (rates) and the element from it that contains the ISO code we are looking for in its sub-element "iso3". Once we have found the element, we need its "kurswert" (rate value) component.

This results in the path "$.kurse[].kurswert", whereby the currency being searched for must still be entered in the square brackets. This means first of all comes "?()" to formulate a query in whose brackets the respective element of "kurse" is then designated with "@", so that "@.iso3" means "the field "iso3" in the current element". If this corresponds to the identifier of the desired currency, e.g. USD ("@.iso3 == 'USD'), it is selected. Our predicate is therefore "?(@.iso3 == 'USD')" for US dollars. When inserted into the path, the result is

$.kurse[?(@.iso3 == 'USD')].kurswert to achieve the exchange rate for US dollars.

The JsonPath object provides a read() method that takes the json to be examined and the json path as a string. Since "$" is used as a placeholder in Groovy, it must be masked with a backslash. Since the return of the REST call is in the processing context, it (and therefore also the json) can be accessed via the object g_sharedState object: g_sharedState.getExchangeRates.body.json

The call can then be made as follows:

JsonPath.read(g_sharedState.getExchangeRates.body.json, "\$.kurse[?(@.iso3 == 'USD')].kurswert")[0]

The JsonPath object attempts to determine a suitable return and in this case offers an array with only one value (the exchange rate). However, since the array itself is not required, but only the value in it, it can be accessed with [0] for the first value in accordance with the usual counting starting with 0.

The result can then be saved in the processing context. The same dot notation can be used for this as for reading: g_sharedState.usd thus creates a place where the exchange rate for USD can be saved in the processing context. After this, it can then be called up under the name "usd". The same procedure can be used for the other values:

g_sharedState.usd = JsonPath.read(g_sharedState.getExchangeRates.body.json, "\$.kurse[?(@.iso3 == 'USD')].kurswert")[0];

Below you will find the Groovy script with the information on the Json path.

// JsonPath-Bibliothek importieren, um Daten aus dem Json im Verarbeitungskontext zu extrahieren

import com.jayway.jsonpath.JsonPath;

// Wechselkurse anhand des ISO3-Codes aus dem Json wählen und im Verarbeitungskontext ablegen
//
// Struktur des Json-Pfads:
// $.kurse[?(@.iso3 == 'GBP')].kurswert
// ^   ^           ^               ^
// |   |           |               Darin das interessierende Feld "kurswert"
// |   |           Abfrage: im Objekt aus der Liste "kurse" soll der Eintrag bei "iso3" dem Wert "GPB" entsprechen
// |   Referenz auf das untergeordnete "kurse"-Objekt
// Referenz auf das Json-Objekt
//
// Anmerkung: Im String für JsonPath.read() muss $ mit \ maskiert werden, da es
// in Groovy sonst als Platzhalter interpretiert wird
//
// Die JsonPath.read()-Abfrage liefert ein Array mit nur einem Eintrag zurück (dem Kurswert), dieser wird mit [0] erfasst

// Im Verarbeitungskontext (shared state) werden vier Objekte angelegt für USD, GBP, JPY und CNY
g_sharedState.usd = JsonPath.read(g_sharedState.getExchangeRates.body.json, "\$.kurse[?(@.iso3 == 'USD')].kurswert")[0];
g_sharedState.gbp = JsonPath.read(g_sharedState.getExchangeRates.body.json, "\$.kurse[?(@.iso3 == 'GBP')].kurswert")[0];
g_sharedState.jpy = JsonPath.read(g_sharedState.getExchangeRates.body.json, "\$.kurse[?(@.iso3 == 'JPY')].kurswert")[0];
g_sharedState.cny = JsonPath.read(g_sharedState.getExchangeRates.body.json, "\$.kurse[?(@.iso3 == 'CNY')].kurswert")[0];

Enter values in a data group ()

In the final step, the four values in the processing context are to be saved in the application's data group. This can be implemented with a data group action that can enter values in a data group.

Since the data group of the application only contains one line, it is not necessary to use a filter to determine which line the data should be written to. Instead, it is sufficient to select under "General" that the action is intended to change a data record (under "Add", a new line would be inserted for each call instead).

The data group of the application can then be selected under "Target data group".

The "Manipulation quantity" tab contains information on which rows of the data group should contain the new values. Since we only have one line in the data set, it is sufficient here to not set a filter. The change is then applied to all rows in the data group.

The field assignment specifies which values are entered in which columns.

A user-defined value can be created as the source in the right-hand window.

Under "System value", a value can be retrieved from the processing context, the name of which (here: the names of the currency from the Groovy script) must still be specified.

After assignment to the target columns (left-hand window), the values are updated in the data group every time the process is executed.

Connecting application and process

In order to trigger the process by clicking on the "Get rates" button, the button is linked to a Javascript function.

The call for a JavaScript function, which we will call "execute()", is stored on the "Script" tab in the properties of the button.

This function can then be inserted in the Javascript editor.

The "execute()" function calls triggerUserWorkflowEvent with the GUID of the event handler and registers two functions that are executed when the process has been successfully completed or an error has been reported. Since the page should refresh after clicking on the button and display the new courses, if successful location.reload() can be called up here.

Below you will find the JavaScript code.

function execute() {
	$.when(triggerUserWorkflowEvent('40AD0EE848B0B4765FE94A2E67E38C9DCBA8D109'))
	.done(function()
		{
			// Bei erfolgreicher Workflow-Ausführung: Seite neu laden
			location.reload();
		}
	)
	.fail(function()
		{
			// Bei fehlerhafter Workflow-Ausführung: Meldung und ggf. Fehlerbehandlung
			alert("Fehler bei der Ausführung.");
		}
	);
	return true;
}