The purpose of this document is to discuss using the Unified REST RA framework to add support for REST APIs to Metaswitch Rhino TAS.

Audience

This document is for developers and solution architects working with REST APIs on the Metaswitch Rhino TAS or Sentinel platforms.

Contents

Introduction

The Rhino REST API Framework provides tools to allow Rhino developers to easily add support for REST APIs to their applications. The Rhino REST API Framework can be used to generate a fully functional REST Resource Adaptor, that supports one or more REST APIs, with minimal developer effort.

Important The Rhino REST API Framework requires access to Metaswitch Artifactory servers. Please contact Metaswitch for a username and password.

This guide explains how to install and set up the Rhino REST API Framework. The steps required to create REST APIs and REST resource adaptors are presented. Finally, the guide explains how to use REST APIs and REST resource adaptors to build Rhino TAS - Telecom Application Server based services.

The guide also includes directions for configuring, monitoring and testing your REST resource adaptors.

Installation and Setup

Prerequisites

The Rhino REST API Framework requires a Linux system with OpenJDK 11. The JAVA_HOME shell environment variable should be set to the base directory of the OpenJDK installation.

Use Rhino 3.0.0.0 or later.

Important The Rhino REST API Framework requires access to Metaswitch Artifactory servers. Please contact Metaswitch for a username and password.
Important Contact Metaswitch if you require a license file.

Preparing to use Rhino REST API Framework

Installation and setup is simple:

1

Download the SDK package

Uncompress the archive unified-rest-ra-sdk-1.0.0.0.zip

~/work$ unzip unified-rest-ra-sdk-1.0.0.0.zip
~/work$ cd unified-rest-ra-sdk
~/work/unified-rest-ra-sdk$ ls -l
total 80
drwxr-xr-x@ 14 sdkuser  staff   448  2 Nov 11:09 .
drwxr-xr-x   4 sdkuser  staff   128  2 Nov 11:07 ..
-rw-r--r--@  1 sdkuser  staff    24 23 Oct 14:16 .build
-rw-r--r--@  1 sdkuser  staff    73 23 Oct 14:16 .gitignore
-rw-r--r--@  1 sdkuser  staff    21 23 Oct 14:16 .sdk.root
-rw-r--r--@  1 sdkuser  staff   267 23 Oct 14:16 README.txt
drwxr-xr-x@ 25 sdkuser  staff   800  2 Nov 11:07 build
-rw-r--r--@  1 sdkuser  staff   391 23 Oct 14:16 build.xml
-rw-r--r--@  1 sdkuser  staff   293 23 Oct 14:16 deps.properties
-rw-r--r--   1 sdkuser  staff   825  2 Nov 11:07 ivy.properties
-rw-r--r--   1 sdkuser  staff  6421  2 Nov 11:09 release.properties
drwxr-xr-x@  7 sdkuser  staff   224 23 Oct 14:16 rhino-sdk
-rw-r--r--@  1 sdkuser  staff  2171 23 Oct 14:16 sdk.properties
drwxr-xr-x@  6 sdkuser  staff   192 23 Oct 14:16 tools
Tip You may freely rename the destination directory unified-rest-ra-sdk

2

Update sdk.properties

There are a few properties in sdk.properties you should update based on your requirements

sdk.properties
# Branch name.
branch.name=trunk 1

# Ivy details to use when publishing modules.
sdk.ivy.org=UNSET 2
sdk.ivy.publish.revision=1.0.0 3

# Component versions to use by default.
# These can be overridden inside individual modules in module.properties.
sdk.component.version=1.0 4
sdk.component.vendor=UNSET 5
  1. the name of the branch, which could be something similar to <yourorg>-rest-ra/trunk

  2. the org value for all Ivy artifacts created by this Rhino REST API Framework SDK install.

  3. the revision value for all Ivy artifacts created by this Rhino REST API Framework SDK install.

  4. the version portion of the SLEE Component ID for all SLEE components published by this Rhino REST API Framework SDK install.

  5. the vendor portion of the SLEE Component ID for all SLEE components published by this Rhino REST API Framework SDK install.

Example values for an ExampleCo Rhino REST API Framework project
branch.name=exampleco-rest-ra/trunk

sdk.ivy.org=exampleco
sdk.ivy.publish.revision=1.0.0

sdk.component.version=1.0
sdk.component.vendor=ExampleCo

3

Run ant for the first time

When you run ant for the first time, the Rhino REST API Framework initialises itself by downloading components from artifactory servers. You will be prompted for a username and password.

Important Contact Metaswitch for a username and password to access Metaswitch Artifactory servers.
~/work/unified-rest-ra-sdk$ ant
Buildfile: /Users/davidp/temp/unified-rest-ra-sdk/build.xml

init-build-extensions:

pre-init-ivy-common:

init-ivy-common:

Determining Ivy settings.

Checking ivy-defaults.properties for ivy settings.
 artifactory.host=repo.opencloud.com                             (from ivy-defaults.properties)
 artifactory.url=https://${artifactory.host}/artifactory         (from ivy-defaults.properties)
 ivy.cache.root=${sdk.root}/build/target/ivy-caches/online-resolvers.cache(from ivy-defaults.properties)
 ivy.checksums=sha1                                              (from ivy-defaults.properties)
 ivy.dir=${basedir}                                              (from ivy-defaults.properties)
 ivy.libs=${target}/libs                                         (from ivy-defaults.properties)
 ivy.local.root=${sdk.root}/repositories/sdk-local               (from ivy-defaults.properties)
 ivy.offline.root=${sdk.root}/repositories/opencloud-offline-mirror(from ivy-defaults.properties)
 ivy.publication.root=${ivy.local.root}                          (from ivy-defaults.properties)
 ivy.resolve.refresh=false                                       (from ivy-defaults.properties)
 ivy.sdk-resolvers.file=online-resolvers.xml                     (from ivy-defaults.properties)
 ivy.sdk-resolvers.path=${ivy.settings.dir}/${ivy.sdk-resolvers.file}(from ivy-defaults.properties)
 ivy.symlinks=false                                              (from ivy-defaults.properties)

Missing Artifactory credentials. An Artifactory username and password are required to access SDK dependencies.
[oc:ivyautoconfigure] Please enter your Artifactory username:
<metaswitch-supplied-username>

Please enter your Artifactory password:

Writing Ivy configuration to: /Users/davidp/temp/unified-rest-ra-sdk/ivy.properties
...

The installation and setup is now complete.

Tip Learn more about the Sentinel Express SDK.

Creating a REST API

A REST API module generates the artifacts related to a particular API such Events, Provider interfaces and so on.

api module workflow.drawio
API Module workflow

Follow these steps to build Rhino support for each REST API.

Step 1: Create an API module

The first step is to use the SDK tool sdkadm to create an API module from a module-pack. The REST RA framework provides a set of module-packs for creating API modules.

Tip
Use the sdkadm list-modules command to list available module-packs.
> list-modules +module-pack
Listing modules based on module tags.

Modules matching all of the following tags will be listed: module-pack

opencloud#clientonly-rest-api#rest-api-framework/1.0.0;1.0.0.0 1
opencloud#clientserver-rest-api#rest-api-framework/1.0.0;1.0.0.0 2
opencloud#raname-unified-rest-ra#rest-api-framework/1.0.0;1.0.0.0 3
opencloud#serveronly-rest-api#rest-api-framework/1.0.0;1.0.0.0 4
  1. module-pack: a resource adaptor type that supports the client role of a REST API

  2. module-pack: a resource adaptor type that supports both the client and server roles of a REST API

  3. module-pack: a unified REST resource adaptor that can support one or more REST APIs

  4. module-pack: a resource adaptor type that supports the server role of a REST API

4

Create an API module

Create an API module with the sdkadm create-module command. For example, to create a server API module for the PingPong API:

sdkadm create-module
> create-module pingpong-rest-api opencloud#serveronly-rest-api#rest-api-framework/1.0.0;1.0.0.0
Extracting '.../rest-api-framework/1.0.0/module-packs/serveronly-rest-api-module-pack-1.0.0.0.zip' to '~/work/unified-rest-ra-sdk/pingpong-rest-api'.

Command line invocation did not contain enough rename arguments to rename all modules.
To specify rename arguments on the command line, include <oldvalue>:<newvalue> pairs as additional arguments.
Missing values will now be prompted for interactively.

sdkadm will prompt you for more details related to the new API (explained in the following steps).

5

The name of the top-level module

The top level module is group module, that encapsulates sub-modules for each of the constituent parts for your API.

The suggested value is the directory name passed in the create-module command. Press return to use the recommended value.

sdkadm top level module
Please enter a name for the top level module, usually this will match the name of the directory for the new module
Rename top level module 'serveronly-rest-api' to [pingpong-rest-api]:

Please enter names for the following sub-module(s) in the module-pack

The following questions prompt you for the names to use for each sub-module and for properties related to your new API. Don’t accept the default values; you need to provide suitable values for each of these questions.

6

The name of the resource adaptor type module

The resource adaptor type module generates a Resource Adaptor Type component that can be deployed in Rhino TAS - Telecom Application Server and is used by a REST resource adaptor.

resource adaptor type module
Rename module 'serveronly-api-server' to [serveronly-api-server]: pingpong-api-server

7

The name of the API plugin module

The API plugin module generates a library component that is used by a REST resource adaptor to implement your REST API. The API plugin library gets deployed in Rhino TAS - Telecom Application Server.

plugin module
Rename module 'serveronly-api-server-plugin' to [serveronly-api-server-plugin]: pingpong-api-server-plugin

8

The name of the API module

The API module contains the OpenAPI specification and an associated configuration file that defines your API. The other API modules depend on this API module.

API module
Rename module 'serveronly-api' to [serveronly-api]: pingpong-api

9

The name of the sbbpart module

The sbb-part module generates a Java library that contains an abstract superclass you can use to write an SBB Part component that can be used in a Rhino TAS - Telecom Application Server based application. The generated class includes all the event handlers and slee annotations you need for your own sbb-part.

SBBPart module
Rename module 'serveronly-api-server-sbbpart' to [serveronly-api-server-sbbpart]: pingpong-api-server-sbbpart

10

The short name of the API, and the root java package for all generated java

API properties
Short name for this API. [pingpong_rest_api]: pingpong
Base Java package for generated API classes. [com.exampleco.openapi.pingpong]:

The create-module command will then create your new REST API module.

create-module for the Ping Ping API finishes …​
Renaming ivy modules and updating dependencies.

Renaming symbolic property references in source files.
Checking "deps.properties" for missing values.

Done. New module(s) should now be available at: ~/work/unified-rest-ra-sdk/pingpong-rest-api
>

The generated modules for the PingPong API is:

pingpong API directory structure
~/work/unified-rest-ra-sdk$ ls -la pingpong-rest-api/
total 32
drwxr-xr-x  10 fwuser  staff   320  2 Nov 16:24 .
drwxr-xr-x@ 16 fwuser  staff   512  2 Nov 16:23 ..
-rw-r--r--   1 fwuser  staff    54  2 Nov 16:24 .sdk.root
-rw-r--r--   1 fwuser  staff   432  2 Nov 16:24 build.xml
-rw-r--r--   1 fwuser  staff  1604  2 Nov 16:24 ivy.xml
-rw-r--r--   1 fwuser  staff   183  2 Nov 16:24 module.properties
drwxr-xr-x   7 fwuser  staff   224  2 Nov 16:24 pingpong-api 1
drwxr-xr-x   7 fwuser  staff   224  2 Nov 16:24 pingpong-api-server 2
drwxr-xr-x   8 fwuser  staff   256  2 Nov 16:24 pingpong-api-server-plugin 3
drwxr-xr-x   7 fwuser  staff   224  2 Nov 16:24 pingpong-api-server-sbbpart 4
  1. This sub-module holds the API specification and a properties file. The other sub-modules have an ivy dependency on this module.

  2. Generates a RA type that includes events, provider interfaces and so on for an RA type that can be deployed in Rhino

  3. Generates a library component the Unified REST RA uses to load and drive the API

  4. Generates a Jar that contains a super-class that developers can extend to create an sbb-part for their application. The generated super-class includes all event handlers and slee-annotations required to deploy such an sbb-part in Rhino.

Tip

Learn about SBB Parts. Learn about JAIN SLEE (JSR 240).

Step 2: Configure the API module

You only need to edit the following files in the API submodule.

  • the API specification file

  • the API properties file

All the required Java components and deployment descriptors are generated by the REST RA framework toolchain.

The generated REST API module contain a default (trivial) api.yaml that is a placeholder for your REST API specification. The generated properties file contains suitable default property values. For example the PingPong (server) API properties are:

PingPong api.properties
# properties related to the package structure of the API

api.shortname = pingpong

# optional - a required license function
api.requiredLicenseFunction =

# packages
api.invokerPackage = com.exampleco.openapi.pingpong
api.apiPackage =     com.exampleco.openapi.pingpong.api
api.modelPackage =   com.exampleco.openapi.pingpong.model
api.implPackage =    com.exampleco.openapi.pingpong.impl

# artifact names
api.server.ratype.artifactname  = pingpong-api-server
api.server.plugin.artifactname  = pingpong-api-server-plugin
api.server.sbbpart.artifactname = pingpong-api-server-sbbpart

Copy your own openapi specification into the API Module and customizes the API properties to specify information like package names, or artifact names and versions. The finished API Module can be checked into source control.

Note
Optional License for your API

One of the properties in the api.properties file (api.requiredLicenseFunction) is the name of a required Metaswitch license key for the API. The unified REST resource adaptor will check for the presence of a license with this license key on resource adaptor activation.

Using this property is optional. The default value means the API does not require a license.

Tip Review the PingPong API openapi specification document.

Step 3: Build the API module

When the API Module builds, it generates code using OpenAPI SLEE Generator and publishes artifacts including:

  • A SLEE RA Type deployable unit, which defines the application (SBB) interface to the REST RA.

  • A REST RA Plugin deployable unit, which provides the implementation used by the REST RA when sending or receiving HTTP messages.

  • A superclass jar for SBB Parts that may be used as a starting point for developing services.

At this point the developer can depend on these generated artifacts in their project, and write service code using the classes and methods from the generated RA Types.

To actually deploy the service and interact with real REST API clients or servers, the developer must also create their REST RA using a REST RA Module.

Tip The developer repeats steps one, two and three as they update their API specification.

Creating a REST Resource Adaptor

A REST RA module generates a Rhino resource adaptor (RA) that has support for one or more REST API (modules).

ra module workflow.drawio
RA Module workflow

Follow these steps to build a resource adaptor, that supports one or more REST APIs, for use in Rhino TAS - Telecom Application Server.

Step 1: Create a REST RA module

The first step is to use the SDK tool sdkadm to create an REST RA module from a module-pack. The REST RA module creates a deployable REST RA.

Tip
Use the sdkadm list-modules command to list available module-packs.
> list-modules +module-pack
Listing modules based on module tags.

Modules matching all of the following tags will be listed: module-pack

opencloud#clientonly-rest-api#rest-api-framework/1.0.0;1.0.0.0 1
opencloud#clientserver-rest-api#rest-api-framework/1.0.0;1.0.0.0 2
opencloud#raname-unified-rest-ra#rest-api-framework/1.0.0;1.0.0.0 3
opencloud#serveronly-rest-api#rest-api-framework/1.0.0;1.0.0.0 4
  1. module-pack: a resource adaptor type that supports the client role of a REST API

  2. module-pack: a resource adaptor type that supports both the client and server roles of a REST API

  3. module-pack: a unified REST resource adaptor that can support one or more REST APIs

  4. module-pack: a resource adaptor type that supports the server role of a REST API

11

Create a REST RA module

Create a REST RA module with the sdkadm create-module command.

For example, to create a REST RA module called example-rest-ra:

sdkadm create-module
> create-module example-rest-ra opencloud#raname-unified-rest-ra#rest-api-framework/1.0.0;1.0.0.0
Extracting '/Users/davidp/temp/unified-rest-ra-sdk/build/target/ivy-caches/online-resolvers.cache/opencloud/raname-unified-rest-ra/rest-api-framework/1.0.0/module-packs/raname-unified-rest-ra-module-pack-1.0.0.0.zip' to '/Users/davidp/temp/unified-rest-ra-sdk/example-rest-ra'.

Command line invocation did not contain enough rename arguments to rename all modules.
To specify rename arguments on the command line, include <oldvalue>:<newvalue> pairs as additional arguments.
Missing values will now be prompted for interactively.

12

Confirm the name of REST RA module

The suggested value is the directory name passed in the create-module command. Press return to use the recommended value.

sdkadm top level module
Please enter a name for the top level module, usually this will match the name of the directory for the new module
Rename top level module 'raname-unified-rest-ra' to [example-rest-ra]:

The create-module command will then create your new REST RA module.

create-module for the example REST RA finishes …​
Renaming ivy modules and updating dependencies.

Renaming symbolic property references in source files.
Checking "deps.properties" for missing values.

Done. New module(s) should now be available at: ~/work/unified-rest-ra-sdk/example-rest-ra

The generated module for the example REST RA is:

Example REST RA directory
~/work/unified-rest-ra-sdk$ ls -la example-rest-ra/
total 40
drwxr-xr-x   9 fwuser  staff   288  3 Nov 13:53 .
drwxr-xr-x@ 17 fwuser  staff   544  3 Nov 13:53 ..
-rw-r--r--   1 fwuser  staff    54  3 Nov 13:53 .sdk.root
-rw-r--r--   1 fwuser  staff   830  3 Nov 13:53 HOW-TO-ADD-API.txt 1
-rw-r--r--   1 fwuser  staff   695  3 Nov 13:53 build.xml
drwxr-xr-x   3 fwuser  staff    96  3 Nov 13:53 config 2
-rw-r--r--   1 fwuser  staff  2924  3 Nov 13:53 ivy.xml 3
-rw-r--r--   1 fwuser  staff   183  3 Nov 13:53 module.properties
drwxr-xr-x   3 fwuser  staff    96  3 Nov 13:53 src
  1. Instructions for configuring the REST RA

  2. directory with a configuration file that is updated when you add new REST APIs to the REST RA

  3. Ivy configuration file that you edit to define the REST APIs your RA will support

Step 2: Configure the REST RA module

The developer customizes properties in the REST RA Module, and updates its dependencies to include all required API Modules.

There are two steps to follow:

1

Add dependencies to ivy.xml

Add two Ivy dependencies for each REST API in your project that this REST RA should support. The plugin should be in the api-library Ivy conf. The RA type should be in the api-ratype Ivy conf.

For example, to add support for the PingPing API add:

<!-- pingpong api -->
<dependency org="${sdk.ivy.org}"
            name="pingpong-api-server-plugin"
            rev="latest.${ivy.status}"
            conf="api-library, slee-component -> slee-component" />

<dependency org="${sdk.ivy.org}"
            name="pingpong-api-server"
            rev="latest.${ivy.status}"
            conf="api-ratype -> api; slee-component -> du" />
Note

The REST RA build inspects dependencies in the api-library and api-ratype confs and generates a package-info.java for the REST RA.

2

Update config/raname-unfied-rest-ra-config.yaml

This configuration file defines properties such as the tracer level for the RA, IncomingRequestTimeout and so on.

The finished RA Module can be checked into source control.

Step 3: Build the REST RA module

The REST RA Module build retrives the Unified REST RA Core module artifacts and all dependent API Modules artifacts (including SLEE libraries & RA Types). These artifacts are assembled to produce a deployable REST RA that supports all the given APIs.

The developer can now deploy their REST RA into Rhino, and use all of its supported APIs with their service.

Note For an introduction to the architecture of a Unified REST RA see: Rhino REST API Framework Architecture

Generated package-info.java

The following java code snippet is for an example RA that implements the the Pet Store API and the Ping Pong API.

Generated package-info.java for exampleco REST RA
@ResourceAdaptorDeployableUnit(
  securityPermissions = @SecurityPermissions(
    securityPermissionSpec = "grant {... };"
  )
)
@ResourceAdaptor(
  vendorExtensionID = "unified-rest-ra-id",
  raTypes = {
    @ResourceAdaptorTypeReference(
          raType = @ComponentId(name="petstore-api-server", vendor="ExampleCo", version="1.0")), 1
    @ResourceAdaptorTypeReference(
          raType = @ComponentId(name="pingpong-api-server", vendor="ExampleCo", version="1.0")), 2
    @ResourceAdaptorTypeReference(
          raType = @ComponentId(name="rest-api-common", vendor="OpenCloud", version="1.0.0"))
  },
  libraryRefs = {
    @LibraryReference(
          library = @ComponentId(name="base-rest-api-plugin", vendor="OpenCloud", version="1.0.0")),
    @LibraryReference(
          library = @ComponentId(name="petstore-api-server-plugin", vendor="ExampleCo", version="1.0")), 3
    @LibraryReference(
          library = @ComponentId(name="pingpong-api-server-plugin", vendor="ExampleCo", version="1.0")), 4
    @LibraryReference(
          library = @ComponentId(name="rest-ratype-spi", vendor="OpenCloud", version="1.0.0"))
  }
)
@OcAssociatedType(UnifiedRestFrameworkRA.class) 5
package com.opencloud.slee.rest;

import com.opencloud.slee.rest.impl.UnifiedRestFrameworkRA;

import com.opencloud.slee.annotation.*;
import javax.slee.annotation.*;
import javax.slee.annotation.du.*;
  1. @ResourceAdaptorTypeReference for the Pet Store API RA Type

  2. @ResourceAdaptorTypeReference for the Ping Pong API RA Type

  3. @LibraryReference for the Pet Store API plugin

  4. @LibraryReference for the Ping Pong API plugin

  5. @OcAssociatedType references the Unified REST RA core, where other remaining RA relatred annotations are defined

Using REST APIs

Service Visible Framework API

One of the components generated by the Rhino REST API Framework is a resource adaptor type. This component contains all the service visible classes and interfaces you need to write Rhino TAS - Telecom Application Server based services.

rest framework service api
  • A resource adaptor Provider interface for your RA type

  • An ACI Factory object

  • A Client interface for your REST API

  • An API interface for each tag used in your API specification

  • Request and Response Events

  • Model classes and Enums

The following diagram explains the relation between the RA Provider, REST API Client and API interfaces.

rest framework service api model

Each RA Type defines one provider interface. The resource adaptor is responsible for implementing this interface. The provider defines a factory operation for fetching a Client interface. The Client defines a factory operation per API that the REST API defines.

Tip Learn more about Resource Adaptors from JAIN SLEE (JSR 240).

The following sections explain how to use the classes and interfaces in the REST API resource adaptor type.

Getting a provider and ACI factory

Rhino implements the SBB component environment, and provides it to the instances of the SBB component classes through the JNDI interfaces. You access a resource adaptor provider object using a JNDI lookup.

For example the PingPongApiServerSbbPart constructor:

PingPongApiServerSbbPart sbb-part constructor
Unresolved directive in rhino-rest-api-framework-users-guide/using-rest-apis/service-visible-framework-api.adoc - include::/mnt/ephemeral0/workspace/product/ra/rest-api-framework/release-1.0.0/Auto/rest-api-framework-docs/rest-api-framework-public-docs/rhino-rest-api-framework-users-guide/include/PingPongApiServerSbbPart.java[tag=sbbpart-constructor]
Tip Review the generated sbb-part superclass as it contains the required annotations and code for obtaining a provider object.

Getting an API object

The RA provider allows you to create a Client object. For example, the PetstoreApiServerProvider interface is:

public interface PetstoreApiServerProvider {
    /** * Return an API Client instance. * @param configuration the configuration to use for the instance of the API. * @return an PetstoreApiServerApiClient instance. */
PetstoreApiServerApiClient getApiClient(ApiConfiguration configuration);
}

You get a Client interface by calling getApiClient(), passing an ApiConfiguration object as a parameter. The ApiConfiguration object defines the configuration to be used with a generated REST API such as the preferred body type to be used when you create outgoing requests and responses.

Tip You may call getApiClient() more than once, with different ApiConfiguration objects.

The PetstoreApiServerApiClient interface is:

public interface PetstoreApiServerApiClient {

    /** * Return an api object related to the PetsApi API. * @return an PetsApi instance. */
    PetsApi getPetsApi();
}

The client interface has one accessor method per tag/API in your OpenAPI Specification.

Deriving API interfaces from an openapi spec

Each API operation may have an associated list of tags. For example, the List Pets request in the Pet Store API has a tag of Pets. The OpenAPI SLEE Generator uses tags to:

  • group operations into API interfaces

  • generate an accessor operation per API interface, in the Client interface.

Use tags in your openapi specifications to group related operations together in the generated API.

Model types

The OpenAPI SLEE Generator generates Java classes and Enums from the schemas subsection of the components section of your openapi specification.

Tip See: Components.

For example the Pet Store schema defines Pet and Pets, from which OpenAPI SLEE Generator will generate a Pet class.

The generated model types implement Serializable, so can be stored in CMP fields.

Tip In the future these will also be FastSerializable, for more efficient storage in CMP.

Creating and Sending requests

Create and send requests by using an API object. For example, the client API used in the REST API Framework Demonstration includes an NotificationApi interface:

public interface NotificationApi {
    // ...

    // Create a RestRequestBuilder for request: CallDirectionNotification
    RestRequestBuilder createCallDirectionNotificationRequest(
          CallEventNotification callEventNotification);

    // Create a RestRequestBuilder for request: CallEventNotification
    RestRequestBuilder createCallEventNotificationRequest(
          CallEventNotification callEventNotification);

    // Send a request
    OutgoingRestActivity sendRequest(
          RestRequestBuilder requestBuilder) throws IOException;
}

The content-type used is defined in your openapi specification. For example the callDirectionNotification operation is:

/calldirection/notification:
post:
  summary: A new Call Direction notification
  operationId: callDirectionNotification
  tags:
    - notification
  requestBody:
    required: true
    description: call direction notification
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/CallEventNotification"

If an operation supports JSON or XML encoding, the configured preferredBodyType (from the ApiConfiguration) dictates which encoding to use. If no preferredBodyType is specified, then the first JSON-compatible media type, from the list of supported types, is used.

Once the new outgoing request is created, it is sent using the API object’s sendRequest method. This method returns an OutgoingRestActivity to which the application should attach, so it will receive the associated response.

Tip See Sending CallDirection and CallEvent notification requests for an example where a new CallEventNotification request is sent, and the application attaches to the OutgoingRestActivity.

Creating and Sending responses

There are two methods for creating and sending responses:

  1. Create the response by using an API object

  2. Create the response from a Request event

Create the response by using an API object

Create and send responses by using an API object. For example, the Pet Store API PetsApi interface includes the following operations to create responses related to a ShowPetById request:

ShowPetById responses in PetsApi interface
    /** * Create a {@link RestResponseBuilder} for a _200_Success response, related to a ShowPetById request. * @param contentTypeStr the desired Content-Type, as a string. If null, the default Content-Type * will be selected from application/json * based on the value of {@link com.opencloud.slee.rest.common.ApiOptions#preferredBodyType} * in the {@link com.opencloud.slee.rest.common.ApiConfiguration} * @param responseBody a Pet, may be null */
    RestResponseBuilder createShowPetById_200_SuccessResponse(String contentTypeStr,
                                                              Pet responseBody);

    /** * Create a {@link RestResponseBuilder} for a Default response, related to a ShowPetById request. * @param contentTypeStr the desired Content-Type, as a string. If null, the default Content-Type * will be selected from application/json * based on the value of {@link com.opencloud.slee.rest.common.ApiOptions#preferredBodyType} * in the {@link com.opencloud.slee.rest.common.ApiConfiguration} * @param responseBody a Error, may be null */
    RestResponseBuilder createShowPetByIdDefaultResponse(int statusCode,
                                                         String contentTypeStr,
                                                         Error responseBody);

Once the new outgoing response is created, it is sent using the API object’s sendResponse method.

PetsApi sendResponse operation
    /** * Send a response. * @param responseBuilder the response to be sent. * @throws IOException if the response could not be built or sent. */
    void sendResponse(RestResponseBuilder responseBuilder, ActivityContextInterface aci)
            throws IOException;

Create the response from a Request event

The generated request event objects include methods for creating the associated responses. For example, the ShowPetByIdRequest event class includes these methods.

    /** * Create a {@link RestResponseBuilder} for a 200 Success response. * @param contentTypeStr the desired Content-Type, as a string. If null, the default Content-Type * will be selected from application/json * based on the value of {@link com.opencloud.slee.rest.common.ApiOptions#preferredBodyType} * in the {@link com.opencloud.slee.rest.common.ApiConfiguration} * @param responseBody a Pet, may be null */
    public RestResponseBuilder create_200_SuccessResponse(String contentTypeStr,
                                                          Pet responseBody)
    {
        // method implementation not shown
    }

    /** * Create a {@link RestResponseBuilder} for a Default response. * @param contentTypeStr the desired Content-Type, as a string. If null, the default Content-Type * will be selected from application/json * based on the value of {@link com.opencloud.slee.rest.common.ApiOptions#preferredBodyType} * in the {@link com.opencloud.slee.rest.common.ApiConfiguration} * @param responseBody a Error, may be null */
    public RestResponseBuilder createDefaultResponse(int statusCode,
                                                     String contentTypeStr,
                                                     Error responseBody)
    {
        // method implementation not shown
    }

In the following example, process a ShowPetById by querying the Pets DB. If there exists a Pet corresponding to PetId then create a 200 success response (create_200_SuccessResponse("application/json", toShow)). Otherwise create a 501 error response.

MockPetstoreSbb handling a ShowPetById request
    private void handleShowPetByIdRequest(ShowPetByIdRequest request,
                                          ActivityContextInterface aci) throws IOException
    {
        final Pet toShow = getPet(request.getPetId());
        if (null == toShow) {
            tracer.finest("Show pet with id: {} = {}", request.getPetId(), toShow);
            final RestResponseBuilder errorResponse = request.createDefaultResponse(
                    501,"application/json",
                    new Error().code(501).message("There is no pet with id: " + request.getPetId()));
            sendResponse(errorResponse, aci);
        }
        else {
            sendResponse(request.create_200_SuccessResponse("application/json", toShow), aci);
        }
    }

Once the new outgoing response is created, it is sent using the API object’s sendResponse method.

MockPetstoreSbb sending responses
    private void sendResponse(RestResponseBuilder response, ActivityContextInterface aci) {
        try { petsApi.sendResponse(response, aci); }
        catch (IOException e) {
            tracer.finest("Failed to send response. Api = {} , OperationId = {}",
                    response.getApi(), response.getOperationId(), e);
        }
    }

Using the Generated SBBPart superclass

The following is an example SBB that uses an sbbpart written using the generated sbbpart super class for the Ping Pong API.

pingpong api.drawio
Using the Ping Pong API

There are two aspects to this example:

  1. A root SBB that defines an SBB local interface.

  2. An sbb-part that extends the generated sbb-part superclass. The sbb-part will interact with the root SBB via the root SBB local interface.

Tip
Advantages of using the generated sbb-part superclass
  • all required slee annotations are generated by the Rhino REST API Framework

  • the code to access the API provider is generated for you

  • the API event handlers are generated for you

  • simple to extend the generated superclass to implement event handler behaviour

Create an SBB class and SBB local interface

SBB local interface

The sbb-part will interact with the root SBB by invoking methods on its SBB local interface. In this example, the root SBB local interface includes a single operation triggerScenario().

SBB Local interface
public interface TriggerScenario {
    boolean triggerScenario(String api, String scenario);
}
@Library(
    id = @ComponentId(name = "@component.name@",
                    vendor = "@component.vendor@",
                   version = "@component.version@")
)
public interface ExampleRestApiSbbLocal extends TriggerScenario, SbbLocalObject {
}

SBB class

The root SBB implements the business logic of the service. The slee-annotations on the root SBB define the service, root SBB and a dependency on our Ping Pong SBB-Part. In this simple example, the root SBB will write a trace message in response to triggerScenario().

Root SBB
@SleeService( 1
    id = @ComponentId(name = "@component.name@",
                    vendor = "@component.vendor@",
                   version = "@component.version@"),
    rootSbb = @RootSBB(sbb = @ComponentId(name = "example-rest-api-sbb",
                                        vendor = "@component.vendor@",
                                       version = "@component.version@"))
)
@SBB( 2
    vendorExtensionID = "example-rest-api-sbb-id",
    id = @ComponentId(name = "example-rest-api-sbb",
                    vendor = "@component.vendor@",
                   version = "@component.version@"),
    sbbClasses = @SBBClasses(
        localInterface = @SBBLocalInterface(
                interfaceName = "com.exampleco.slee.rest.ExampleRestApiSbbLocal"),
        abstractClass = @SBBAbstractClass(reentrant = true)
    ),
    libraryRefs = {
        @LibraryReference(library = @ComponentId(name = "example-service-sbblocal",
                                               vendor = "@component.vendor@",
                                              version = "@component.version@"))
    }
)
@OcSBB(
    vendorExtensionID = "example-rest-api-sbb-id",
    sbbParts = {
        @SBBPartReference(id = @ComponentId(name = "example-pingpong-api-sbbpart", 3
                                          vendor = "@component.vendor@",
                                          ersion = "@component.version@"))
    },
    sbbClasses = @OcSBBClasses()
)
public abstract class ExampleRestApiSbb implements javax.slee.Sbb, TriggerScenario {
  1. defines the service and identifies the root SBB

  2. the example SBB (the root SBB for the service)

  3. the sbb-part annotation links the root SBB to our Ping Pong sbb-part

Implement the SBB local interface
    @Override
    public boolean triggerScenario(String api, String scenario) {
        tracer.fine("Triggering: api={}, scenario={}", api, scenario);
        return true;
    }

Create an sbb-part

SBB-Part class

The PingpongSbbpart class extends the generated sbb-part superclass (PingpongApiServerSbbpart).

PingpongSbbpart
Unresolved directive in rhino-rest-api-framework-users-guide/using-rest-apis/using-the-generated-sbbpart-superclass.adoc - include::/mnt/ephemeral0/workspace/product/ra/rest-api-framework/release-1.0.0/Auto/rest-api-framework-docs/rest-api-framework-public-docs/rhino-rest-api-framework-users-guide/include/PingpongSbbpart.java[tag=sbbpart-annotation]

Implement sbb-part constructor

The PingpongSbbpart calls the superclass constructor and creates a Ping API object.

PingpongSbbpart sbb-part constructor
Unresolved directive in rhino-rest-api-framework-users-guide/using-rest-apis/using-the-generated-sbbpart-superclass.adoc - include::/mnt/ephemeral0/workspace/product/ra/rest-api-framework/release-1.0.0/Auto/rest-api-framework-docs/rest-api-framework-public-docs/rhino-rest-api-framework-users-guide/include/PingpongSbbpart.java[tag=sbbpart-constructor]

Override protected methods from the generated sbb-part superclass

The generated superclass defines protected methods that our sbb-part overrides to implement the service logic of the service.

Override protected methods from PingpongApiServerSbbpart
Unresolved directive in rhino-rest-api-framework-users-guide/using-rest-apis/using-the-generated-sbbpart-superclass.adoc - include::/mnt/ephemeral0/workspace/product/ra/rest-api-framework/release-1.0.0/Auto/rest-api-framework-docs/rest-api-framework-public-docs/rhino-rest-api-framework-users-guide/include/PingpongSbbpart.java[tag=override-protected-methods]

Implement handlePingRequest by calling triggerScenario() on the sbb-local interface, passing the api and scenario values from the PingReqest.

Understanding the Generated Code in PingPongApiServerSbbPart

The generated sbb-part superclass handles all the necessary wiring code required to access the PingPong REST API.

  • @SBBPart, @RATypeBindings and @SBBPartDeployableUnit slee annotations

  • SBB Part constuctor that does required JNDI lookups for the PingPong REST API provider

  • implements event handlers for each event supported by the PingPong REST API

  • defines protected methods that developers override to implement service logic

PingPongApiServerSbbPart sbb-part annotations
Unresolved directive in rhino-rest-api-framework-users-guide/using-rest-apis/using-the-generated-sbbpart-superclass.adoc - include::/mnt/ephemeral0/workspace/product/ra/rest-api-framework/release-1.0.0/Auto/rest-api-framework-docs/rest-api-framework-public-docs/rhino-rest-api-framework-users-guide/include/PingPongApiServerSbbPart.java[tag=sbbpart-annotation]
PingPongApiServerSbbPart sbb-part constructor
Unresolved directive in rhino-rest-api-framework-users-guide/using-rest-apis/using-the-generated-sbbpart-superclass.adoc - include::/mnt/ephemeral0/workspace/product/ra/rest-api-framework/release-1.0.0/Auto/rest-api-framework-docs/rest-api-framework-public-docs/rhino-rest-api-framework-users-guide/include/PingPongApiServerSbbPart.java[tag=sbbpart-constructor]
PingPongApiServerSbbPart sbb-part PingRequest event handler
Unresolved directive in rhino-rest-api-framework-users-guide/using-rest-apis/using-the-generated-sbbpart-superclass.adoc - include::/mnt/ephemeral0/workspace/product/ra/rest-api-framework/release-1.0.0/Auto/rest-api-framework-docs/rest-api-framework-public-docs/rhino-rest-api-framework-users-guide/include/PingPongApiServerSbbPart.java[tag=eventhandler-request-com.exampleco.openapi.pingpong.api.PingRequest]
  1. slee-annotation for the PingRequest event handler

  2. implementation of the PingRequest event handler

  3. developer overrides this method to define normal behaviour for handling a PingRequest

  4. developer overides this method to define the behaviour when handling a PingRequest fails.

Writing an SBB

The following is an SBB that implements a Pet Store using the Pet Store API.

petstore api.drawio
Implementing a Petstore in Rhino

To develop the Pet Store SBB you must:

Create an SBB class and define slee-annotations

Create an abstract class that implements javax.slee.sbb. This class will be the root SBB of the Pet Store service and should implement the SBB lifecycle operations.

Define slee annotations on your abstract class that define: the Pet Store service, the root sbb and a resource adaptor type binding for the Pet Store REST API.

MockPetstoreSbb annotations
@SleeService(
  id = @ComponentId(name = "@component.name@",
                  vendor = "@component.vendor@",
                 version = "@component.version@"),
  rootSbb = @RootSBB(
                sbb = @ComponentId(name = "@component.name@-sbb",
                                 vendor = "@component.vendor@",
                                version = "@component.version@"))
)
@SBB(
  id = @ComponentId(name = "@component.name@-sbb",
                  vendor = "@component.vendor@",
                 version = "@component.version@"),
  sbbClasses = @SBBClasses(abstractClass = @SBBAbstractClass(reentrant = true)),
  libraryRefs = {
    @LibraryReference(
            library = @ComponentId(name = "@rest-api-common.LibraryID.rest-api-common.name@",
                                 vendor = "@rest-api-common.LibraryID.rest-api-common.vendor@",
                                version = "@rest-api-common.LibraryID.rest-api-common.version@"))
  }
)
@RATypeBindings(
  raTypeBindings = {
    @RATypeBinding(
      raType = @ComponentId(name = "petstore-api-server", vendor = "ExampleCo", version = "1.0"),
      resourceAdaptorObjectName = MockPetstoreSbb.petstore_PROVIDER_JNDI_NAME,
      resourceAdaptorEntityLink = "unified-rest-ra"
    )
  }
)
public abstract class MockPetstoreSbb implements javax.slee.Sbb {

Implement setSbbContext() and other SBB lifecycle operations

Rhino invokes the setSbbContext() method on instances of your SBB abstract class. Initialize state that can be held by the SBB object during its lifetime.

Tip Such state cannot be specific to an SBB entity because the SBB object might be reused during its lifetime to serve multiple SBB entities.

The MockPetstoreSbb creates a Tracer, does a JNDI lookup to get a Pet Store API provider object and finally creates a Pets API object.

MockPetstoreSbb setSbbContext()
    public void setSbbContext(SbbContext context) {
        sbbContext = (RhinoSbbContext) context;
        tracer = (ExtendedTracer) sbbContext.getTracer(sbbContext.getSbb().getName());
        tracer.finest("setSbbContext");
        try {
            final Context env = (Context) new InitialContext().lookup("java:comp/env");
            this.apiProvider = (PetstoreApiServerProvider)
                    env.lookup(petstore_PROVIDER_JNDI_NAME);
        }
        catch (NamingException e) {
            throw new RuntimeException("NamingException locating PetstoreApiServerProvider", e);
        }
        // get a pet store API instance
        this.petsApi = apiProvider.getApiClient(
                ApiConfiguration.standardConfiguration()).getPetsApi();
    }

Implement event handlers

Tip Speed up your development by referring to the generated sbbpart superclass. The generated event handlers in the Petstore API sbbpart superclass can be copied directly to MockPetstoreSbb.

On receipt of a CreatePet request event, call handleCreatePetRequest(). If there is any failure then call rejectCreatePetRequest().

MockPetstoreSbb CreatePetRequest event handler
    @EventMethod(
        eventType = @ComponentId(name = "com.exampleco.petstore.api.CreatePetRequest",
                               vendor = "ExampleCo",
                              version = "1.0"),
        initialEvent = true,
        initialEventSelectVariable = IESVariableType.ActivityContext
    )
    public void onCreatePetRequest(CreatePetRequest request,
                                   ActivityContextInterface aci,
                                   EventContext eventContext)
    {
        tracer.finest("Received: {}", request);
        try {
            handleCreatePetRequest(request, aci);
        }
        catch(IOException e) {
            tracer.finest("Failed to handle: {}", request, e);
            rejectCreatePetRequest(request, aci);
        }
    }

Process a CreatePet request by updating the Pets DB. If updating the DB is successful, then send a success response (request.create_204_SuccessResponse()). Otherwise send a 501 error response.

MockPetstoreSbb handling a CreatePet request
    private void handleCreatePetRequest(CreatePetRequest request,
                                        ActivityContextInterface aci) throws IOException
    {
        if (addPet(request.getPet())) {
            tracer.finest("Added pet: {}", request.getPet());
            sendResponse(request.create_204_SuccessResponse(), aci);
        }
        else {
            final RestResponseBuilder errorResponse = request.createDefaultResponse(
                    501,"application/json",
                    new Error().code(501)
                            .message("Pet already exists with id: " + request.getPet().getId()));
            sendResponse(errorResponse, aci);
        }
    }

Reject a CreatePet request, by sending a 501 error response.

MockPetstoreSbb rejecting a CreatePet request
    private void rejectCreatePetRequest(CreatePetRequest request, ActivityContextInterface aci) {
        final RestResponseBuilder errorResponse = request.createDefaultResponse(
                501,"application/json",
                new Error().code(501).message("Failed to create pet: " + request.getPet()));
        sendResponse(errorResponse, aci);
    }
MockPetstoreSbb sending responses
    private void sendResponse(RestResponseBuilder response, ActivityContextInterface aci) {
        try { petsApi.sendResponse(response, aci); }
        catch (IOException e) {
            tracer.finest("Failed to send response. Api = {} , OperationId = {}",
                    response.getApi(), response.getOperationId(), e);
        }
    }

Implementation of the ListPetsRequest and ShowPetByIdRequest event handlers follows the same pattern.

MockPetstoreSbb ListPetsRequest event handler
    @EventMethod(
        eventType = @ComponentId(name = "com.exampleco.petstore.api.ListPetsRequest",
                               vendor = "ExampleCo",
                              version = "1.0"),
        initialEvent = true,
        initialEventSelectVariable = IESVariableType.ActivityContext
    )
    public void onListPetsRequest(ListPetsRequest request,
                                  ActivityContextInterface aci,
                                  EventContext eventContext)
    {
        tracer.finest("Received: {}", request);
        try {
            handleListPetsRequest(request, aci);
        }
        catch(IOException e) {
            tracer.finest("Failed to handle: {}", request, e);
            rejectListPetsRequest(request, aci);
        }
    }
MockPetstoreSbb ShowPetByIdRequest event handler
    @EventMethod(
        eventType = @ComponentId(name = "com.exampleco.petstore.api.ShowPetByIdRequest",
                               vendor = "ExampleCo",
                              version = "1.0"),
        initialEvent = true,
        initialEventSelectVariable = IESVariableType.ActivityContext
    )
    public void onShowPetByIdRequest(ShowPetByIdRequest request,
                                     ActivityContextInterface aci,
                                     EventContext eventContext)
    {
        tracer.finest("Received: {}", request);
        try {
            handleShowPetByIdRequest(request, aci);
        }
        catch(IOException e) {
            tracer.finest("Failed to handle: {}", request, e);
            rejectShowPetByIdRequest(request, aci);
        }
    }

Configuring the REST Resource Adaptor

Below are properties for configuring unified REST resource adaptor, including properties for secure configuration.

Name Type Default Description

ListenAddress

String

127.0.0.1

Local IP address that the Unified REST resource adaptor will listen on for requests.

ListenPort

Integer

8000

Local TCP port that the Unified REST resource adaptor will listen on for requests.

BindAddresses

String

Contains per-node configuration information regarding each node’s specified local IP address and local TCP port that the Unified REST resource adaptor will listen on for requests. If BindAddresses is empty, ListenAddress and Listen Port will be employed. For syntax see Specifying bind addresses.

MaxOutgoingConnections

Integer

5

The maximum number of outgoing TCP connections the resource adaptor will open to a single host when sending requests to that host.

OutgoingRequestTimeout

Long

10000

The time (in ms) that the resource adaptor will wait for a response to an outgoing request before failing the request. The application will see a 500 Server Error response and in case of asynchronous request, activity will end.

OutgoingIdleTimeout

Long

60000

The time (in ms) that the resource adaptor will keep an outgoing TCP connection open for, with no activity on the connection.

MaxDepth

Integer

1

Controls how many HTTP requests may be queued up on a single connection without receiving a response from the server. This is known as "HTTP pipelining". A value of 1 is strongly recommended if the server does not support non-persistent connections. When the connection closes (which it always will if they are non-persistent) the other requests in the pipeline will have to be re-sent on another connection. A value of 1 is also recommended for slow or unreliable servers, to avoid head-of-line blocking.

QueueTimeout

Long

5000

The time (in ms) that a request can be held on the queue waiting for an outgoing connection before timing out and firing an event with response code 500 and reason "Timed out waiting for server connection".

NewConnectionDelay

Long

100

When a burst of (send) requests occurs, the resource adaptor queues them, and only creates new connections if they cannot be sent on existing connections. NewConnectionDelay is the time in milliseconds that the resource adaptor will wait for an outgoing connection to become available before creating a new one. If the server does not support persistent connections, it is strongly recommended that this value be set to 0.

IncomingRequestTimeout

Long

5000

The time (in ms) that the resource adaptor will wait for the SLEE to send a response to an incoming request before failing the request. The resource adaptor will automatically send an error response to the client and end the activity.

IncomingIdleTimeout

Long

60000

The time (in ms) that the resource adaptor will keep an incoming TCP connection open for, with no activity on the connection.

ServerName

String

Rhino-Unified-REST-Server/1.0

The default value of the Server header set in outgoing responses. Applications may override this by setting the Server header in the response manually.

UserAgent

String

Rhino-Unified-REST-Client/1.0

The default value of the User-Agent header set in outgoing requests. Applications may override this by setting the User-Agent header in the request manually.

AddressType

String

null

Defines how the resource adaptor generates the javax.slee.Address object for HTTP events. Default is to not use any address. Setting this to the value uripath will make the resource adaptor use a URI address with the value of the path in the request URL. For example, http://localhost/test/zzz?query=yyy will create an address with address plan AddressPlan.URI and value "/test/zzz". Responses have no address.

MaxContentLength

Integer

1048576

Maximum value in bytes for the Content-Length header of the HTTP messages which have a message body. If this value is exceeded, the application will see a 413 Client Error Response. MaxContentLength value update will apply to new connections.

WorkerCount

Integer

0

The number of workers used by the server and client worker executors. 0 means use Netty default: 2 * untime.availableProcessors()

AutomaticContentCompression

Boolean

false

If enabled, the Unified REST resource adaptor will compress outgoing responses automatically, while respecting the Accept-Encoding header (see RFC 2616 - section 14.3 for details).

AutomaticContentDecompression

Boolean

true

If enabled, the Unified REST resource adaptor will decompress incoming responses automatically, while respecting the Content-Encoding header (see RFC 2616 - section 14.11 for details).

CreateIncomingSasTrail

Boolean

true

If enabled, the Unified REST resource adaptor will create SAS Trails for incoming requests, and report them automatically.

DefaultNodeCallbackUrl

String

http://localhost:8000

The default URL the Unified REST resource adaptor will provide to services, to include as a callback URL that identifies the current cluster node.

DefaultClusterCallbackUrl

String

http://localhost:8000

The default URL the Unified REST resource adaptor will provide to services, to include as a callback URL that identifies the cluster.

Note ThreadPoolSize, MaxChannelMemorySize and MaxTotalMemorySize are not currently used. In future, they will support configuration of the Netty OrderedMemoryAwareThreadPoolExecutor.

Secure configuration

Name Type Default Description

SecureListenPort

Integer

0

Local TCP port that the Unified REST resource adaptor will listen on for HTTPS requests. If set to a value greater than 0, then a KeyStore must be provided that contains the private key for the server.

SecureBindAddresses

String

Contains per-node configuration information regarding each node’s specified local IP address and local TCP port that the Unified REST resource adaptor will listen on for requests. If SecureBindAddresses is empty, ListenAddress and SecureListenPort will be employed. If a SecureBindAddress is specified then a KeyStore must be provided that contains the private key for the server. For syntax see Specifying bind addresses.

KeyStore

String

(empty)

Path to JKS keystore file. System properties (such as ${rhino.dir.home}) may be used.

KeyStorePassword

String

(empty)

Keystore password for file specified by KeyStore.

TrustStore

String

${java.home}/lib/security/cacerts

Path to JKS keystore for trust certificates. The special value <<TRUST ALL>> may be used to bypass trust checks.

TrustStorePassword

String

changeit

Keystore password for file specified by TrustStore.

CipherSuites

String

(empty)

List of cipher suites to pass to SSLEngine.setEnabledCipherSuites(). An empty value means the method won’t be called and all cipher suites are enabled.

SSLSessionTimeout

Integer

0

Value to pass to SSLContext.setSessionTimeout() after the context is created. A value of 0 means the method won’t be called.

NeedClientAuth

Boolean

false

When set to true, this will set the same flag on the SSL Engine for incoming HTTPS connections, meaning a valid client certificate is required to connect. The client certificates can be retrieved from the HttpRequest event using the getClientDetails() method.

Specifying bind addresses

BindAddresses and SecureBindAddresses are specified as a comma-separated list of {node}host:port elements.

This allows entities running on two nodes on the same host to use different ports, for example:

{101}0.0.0.0:8000,{102}0.0.0.0:8001

It also allows entities running on different hosts to specify an interface to listen on that is specific to each host, for example:

{101}192.168.1.100:8000,{102}192.168.1.101:8000

Deploying a REST Resource Adaptor

To deploy your REST resource adaptor your need to:

  • start a Rhino TAS instance

  • use the Ivy based install to deploy and configure your resource adaptor.

Starting a Rhino TAS SDK

The Rhino REST API Framework includes all you require to use a Rhino TAS SDK. At the root of your Rhino REST API Framework project directory is a rhino-sdk directory. Run ant -p in the rhino-sdk to list all possible targets.

~/work/unified-rest-ra-sdk/rhino-sdk$ ant -p
Buildfile: /home/davidp/work/unified-rest-ra-sdk/rhino-sdk/build.xml

Main targets:

 clean-rhino        Deletes the Rhino SDK's work/ directory. This deletes all logs and deployment state.
 install-rhino      Installs the Rhino SDK into RhinoSDK/.
 start-clean-rhino  Convenience target to run 'clean-rhino' and 'start-rhino'.
 start-rhino        Starts the Rhino SDK.
 stop-rhino         Stops the Rhino SDK.
 uninstall-rhino    Uninstalls (deletes) the Rhino SDK from RhinoSDK/.
Default target: start-rhino

When you run ant for the first time, a Rhino TAS SDK install package is downloaded and installed.

Tip Learn more about the Rhino TAS SDK in the Rhino SDK Getting Started Guide.

Deploying your REST resource adaptor

You can deploy and configure your REST resource adaptor directly from the resource adaptor module. For example to deploy the example-rest-ra described in Creating a REST Resource Adaptor page:

Step 1: Run ant deploy-with-deps

running ant deploy-with-deps for the example REST ra
~/work/unified-rest-ra-sdk/example-rest-ra$ ant deploy-with-deps
Buildfile: /home/davidp/work/unified-rest-ra-sdk/example-rest-ra/build.xml

deploy-with-deps:
     [echo] Deploying module.
[oc:deploy] :: loading settings :: file = /home/davidp/work/unified-rest-ra-sdk/build/ivy/ivysettings.xml
[oc:deploy] Created deployer with options: OutdatedIvyModuleDetection: Enabled, IvyStatusesToCheck: [integration]
[oc:deploy] Invoking the deployer to process root module exampleco#example-rest-ra#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp and its dependencies ...
[oc:deploy] Installing module opencloud#guava-library#third-party;28.2-jre
[oc:deploy] Installing module opencloud#jackson-library#third-party;2.11.0
[oc:deploy] Installing module opencloud#jaxb-api-library#third-party;2.4.0-b2
[oc:deploy] Installing module opencloud#jackson-dataformat-xml-library#third-party;2.11.0
[oc:deploy] Installing module opencloud#rest-api-common#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] Installing module exampleco#pingpong-api-server#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp
[oc:deploy] Installing module opencloud#rest-ratype-spi#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] Installing module exampleco#pingpong-api-server-plugin#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp
[oc:deploy] Installing module opencloud#base-rest-api-plugin#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] Installing module opencloud#netty-library#third-party;4.1.45
[oc:deploy] Installing module opencloud#unified-rest-ra-core#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] Installing module exampleco#example-rest-ra#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp
[oc:deploy] Deployment Result:
[oc:deploy] ---------------------------------------------------------------------
[oc:deploy] |  Deploy result:
[oc:deploy] ---------------------------------------------------------------------
[oc:deploy] |  Modules with no Component:
[oc:deploy] |  opencloud#unified-rest-ra-core#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] ---------------------------------------------------------------------
[oc:deploy] |  Deployed Modules:
[oc:deploy] |  opencloud#jackson-dataformat-xml-library#third-party;2.11.0
[oc:deploy] |  |__ LibraryID[name=jackson-dataformat-xml,vendor=com.fasterxml.jackson.dataformat,version=2.11.0]
[oc:deploy] |  opencloud#jaxb-api-library#third-party;2.4.0-b2
[oc:deploy] |  |__ LibraryID[name=jaxb-api,vendor=javax.xml.bind,version=2.4.0-b2]
[oc:deploy] |  opencloud#guava-library#third-party;28.2-jre
[oc:deploy] |  |__ LibraryID[name=guava,vendor=com.google.guava,version=28.2-jre]
[oc:deploy] |  opencloud#rest-ratype-spi#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] |  |__ LibraryID[name=rest-ratype-spi,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |  exampleco#pingpong-api-server-plugin#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp
[oc:deploy] |  |__ LibraryID[name=pingpong-api-server-plugin,vendor=ExampleCo,version=1.0]
[oc:deploy] |  exampleco#pingpong-api-server#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp
[oc:deploy] |  |__ LibraryID[name=pingpong-api-server,vendor=ExampleCo,version=1.0]
[oc:deploy] |      ResourceAdaptorTypeID[name=pingpong-api-server,vendor=ExampleCo,version=1.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.openapi.pingpong_rest_api.api.DummyOperationRequest,vendor=ExampleCo,version=1.0]
[oc:deploy] |  opencloud#rest-api-common#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] |  |__ EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.POST,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.TRACE,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      LibraryID[name=rest-api-common,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.GET,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.DELETE,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      ResourceAdaptorTypeID[name=rest-api-common,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.HEAD,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestResponse,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.PATCH,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.PUT,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.OPTIONS,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |  opencloud#netty-library#third-party;4.1.45
[oc:deploy] |  |__ LibraryID[name=netty,vendor=io.netty,version=4.1.45]
[oc:deploy] |  exampleco#example-rest-ra#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp
[oc:deploy] |  |__ ResourceAdaptorID[name=example-rest-ra,vendor=ExampleCo,version=1.0]
[oc:deploy] |  opencloud#base-rest-api-plugin#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] |  |__ LibraryID[name=base-rest-api-plugin,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |  opencloud#jackson-library#third-party;2.11.0
[oc:deploy] |  |__ LibraryID[name=jackson,vendor=com.fasterxml.jackson.core,version=2.11.0]
[oc:deploy] ---------------------------------------------------------------------
[oc:deploy] All modules deployed successfully.
   [delete] Deleting directory /home/davidp/work/unified-rest-ra-sdk/example-rest-ra/target/deployer-work

BUILD SUCCESSFUL
Total time: 14 seconds

Step 2: Run ant configure-with-deps

running ant configure-with-deps for the example REST ra
~/work/unified-rest-ra-sdk/example-rest-ra$ ant deploy-with-deps
Buildfile: /home/davidp/work/unified-rest-ra-sdk/example-rest-ra/build.xml

deploy-with-deps:
     [echo] Deploying module.
[oc:deploy] :: loading settings :: file = /home/davidp/work/unified-rest-ra-sdk/build/ivy/ivysettings.xml
[oc:deploy] Created deployer with options: OutdatedIvyModuleDetection: Enabled, IvyStatusesToCheck: [integration]
[oc:deploy] Invoking the deployer to process root module exampleco#example-rest-ra#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp and its dependencies ...
[oc:deploy] Installing module opencloud#guava-library#third-party;28.2-jre
[oc:deploy] Installing module opencloud#jackson-library#third-party;2.11.0
[oc:deploy] Installing module opencloud#jaxb-api-library#third-party;2.4.0-b2
[oc:deploy] Installing module opencloud#jackson-dataformat-xml-library#third-party;2.11.0
[oc:deploy] Installing module opencloud#rest-api-common#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] Installing module exampleco#pingpong-api-server#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp
[oc:deploy] Installing module opencloud#rest-ratype-spi#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] Installing module exampleco#pingpong-api-server-plugin#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp
[oc:deploy] Installing module opencloud#base-rest-api-plugin#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] Installing module opencloud#netty-library#third-party;4.1.45
[oc:deploy] Installing module opencloud#unified-rest-ra-core#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] Installing module exampleco#example-rest-ra#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp
[oc:deploy] Deployment Result:
[oc:deploy] ---------------------------------------------------------------------
[oc:deploy] |  Deploy result:
[oc:deploy] ---------------------------------------------------------------------
[oc:deploy] |  Modules with no Component:
[oc:deploy] |  opencloud#unified-rest-ra-core#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] ---------------------------------------------------------------------
[oc:deploy] |  Deployed Modules:
[oc:deploy] |  opencloud#jackson-dataformat-xml-library#third-party;2.11.0
[oc:deploy] |  |__ LibraryID[name=jackson-dataformat-xml,vendor=com.fasterxml.jackson.dataformat,version=2.11.0]
[oc:deploy] |  opencloud#jaxb-api-library#third-party;2.4.0-b2
[oc:deploy] |  |__ LibraryID[name=jaxb-api,vendor=javax.xml.bind,version=2.4.0-b2]
[oc:deploy] |  opencloud#guava-library#third-party;28.2-jre
[oc:deploy] |  |__ LibraryID[name=guava,vendor=com.google.guava,version=28.2-jre]
[oc:deploy] |  opencloud#rest-ratype-spi#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] |  |__ LibraryID[name=rest-ratype-spi,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |  exampleco#pingpong-api-server-plugin#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp
[oc:deploy] |  |__ LibraryID[name=pingpong-api-server-plugin,vendor=ExampleCo,version=1.0]
[oc:deploy] |  exampleco#pingpong-api-server#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp
[oc:deploy] |  |__ LibraryID[name=pingpong-api-server,vendor=ExampleCo,version=1.0]
[oc:deploy] |      ResourceAdaptorTypeID[name=pingpong-api-server,vendor=ExampleCo,version=1.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.openapi.pingpong_rest_api.api.DummyOperationRequest,vendor=ExampleCo,version=1.0]
[oc:deploy] |  opencloud#rest-api-common#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] |  |__ EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.POST,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.TRACE,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      LibraryID[name=rest-api-common,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.GET,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.DELETE,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      ResourceAdaptorTypeID[name=rest-api-common,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.HEAD,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestResponse,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.PATCH,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.PUT,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |      EventTypeID[name=com.opencloud.slee.rest.common.RestRequest.OPTIONS,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |  opencloud#netty-library#third-party;4.1.45
[oc:deploy] |  |__ LibraryID[name=netty,vendor=io.netty,version=4.1.45]
[oc:deploy] |  exampleco#example-rest-ra#exampleco-rest-ra/trunk;1.0.0.0-DEV1-davidp
[oc:deploy] |  |__ ResourceAdaptorID[name=example-rest-ra,vendor=ExampleCo,version=1.0]
[oc:deploy] |  opencloud#base-rest-api-plugin#rest-api-framework/1.0.0;1.0.0.0
[oc:deploy] |  |__ LibraryID[name=base-rest-api-plugin,vendor=OpenCloud,version=1.0.0]
[oc:deploy] |  opencloud#jackson-library#third-party;2.11.0
[oc:deploy] |  |__ LibraryID[name=jackson,vendor=com.fasterxml.jackson.core,version=2.11.0]
[oc:deploy] ---------------------------------------------------------------------
[oc:deploy] All modules deployed successfully.
   [delete] Deleting directory /home/davidp/work/unified-rest-ra-sdk/example-rest-ra/target/deployer-work

BUILD SUCCESSFUL
Total time: 14 seconds

As a last step, use rhino-console to inspect the state of your Rhino TAS SDK.

Inspecting Rhino to see what is installed
~/work/unified-rest-ra-sdk/rhino-sdk/RhinoSDK/client/bin$ ./rhino-console
Interactive Rhino Management Shell
Rhino management console, enter 'help' for a list of commands
Connected to node 101
[admin@localhost (#0)] listresourceadaptors
ResourceAdaptorID[name=example-rest-ra,vendor=ExampleCo,version=1.0] 1
[admin@localhost (#1)] listraentities
example-rest-ra  2
[admin@localhost (#2)] listraentityconfigproperties example-rest-ra
Configuration properties for resource adaptor entity example-rest-ra:  3
 AddressType (java.lang.String):
 AutomaticContentCompression (java.lang.Boolean): false
 AutomaticContentDecompression (java.lang.Boolean): true
 BindAddresses (java.lang.String):
 CipherSuites (java.lang.String):
 CreateIncomingSasTrail (java.lang.Boolean): true
 DefaultClusterCallbackUrl (java.lang.String): http://localhost:8000
 DefaultNodeCallbackUrl (java.lang.String): http://localhost:8000
 IncomingIdleTimeout (java.lang.Long): 60000
 IncomingRequestTimeout (java.lang.Long): 60000
 KeyStore (java.lang.String):
 KeyStorePassword (java.lang.String):
 ListenAddress (java.lang.String): localhost
 ListenPort (java.lang.Integer): 8000
 MaxChannelMemorySize (java.lang.Long): 10485760
 MaxContentLength (java.lang.Integer): 1048576
 MaxDepth (java.lang.Integer): 1
 MaxOutgoingConnections (java.lang.Integer): 30
 MaxTotalMemorySize (java.lang.Long): 0
 NeedClientAuth (java.lang.Boolean): false
 NewConnectionDelay (java.lang.Long): 100
 OutgoingIdleTimeout (java.lang.Long): 60000
 OutgoingRequestTimeout (java.lang.Long): 10000
 QueueTimeout (java.lang.Long): 5000
 SSLSessionTimeout (java.lang.Integer): 0
 SecureBindAddresses (java.lang.String):
 SecureListenPort (java.lang.Integer): 0
 ServerName (java.lang.String): Rhino-HTTP-Server/1.0
 ThreadPoolSize (java.lang.Integer): 10
 TrustStore (java.lang.String): ${java.home}/lib/security/cacerts
 TrustStorePassword (java.lang.String): changeit
 UserAgent (java.lang.String): Rhino-HTTP-Client/1.0
 WorkerCount (java.lang.Integer): 0
 slee-vendor:com.opencloud.rhino_max_activities (java.lang.Integer): 0
 slee-vendor:com.opencloud.rhino_replicate_activities (java.lang.String): none
[admin@localhost (#3)] listresourceadaptortypes 4
ResourceAdaptorTypeID[name=SessionOwnership,vendor=OpenCloud,version=1.0]
ResourceAdaptorTypeID[name=pingpong-api-server,vendor=ExampleCo,version=1.0]
ResourceAdaptorTypeID[name=rest-api-common,vendor=OpenCloud,version=1.0.0]
  1. The resource adaptors installed (via deploy-with-deps)

  2. The resource adaptor entity that was created (via configure-with-deps)

  3. The configuration of the new resource adaptor entity

  4. The resource adaptor types installed

Service Assurance Server (SAS) Tracing

The Unified REST resource adaptors integrate with the Metaswitch Service Assurance Server (SAS). SAS provides a mechanism to record and search detailed end-to-end tracing of call handling.

See the SAS Facility section in the Rhino extended API documentation for more information about SAS tracing.

Note Rhino versions 2.6 and later support SAS tracing.

SAS tracing of incoming requests

The resource adaptor will create a new SAS trail for every incoming request, if configured to do so. This is the default behaviour. The incoming request will be recorded on this trail.

Tip The CreateIncomingSasTrail configuration property controls this behaviour. See Configuring the REST Resource Adaptor.

SAS tracing of outgoing requests

If there is a SAS trail attached to the OutgoingRestActivity, the REST resource adaptor will use the SAS trail to record the outgoing request. The REST resource adaptor will never create a SAS trail, so if there is no trail attached to the OutgoingRestActivity, then the request will not be recorded.

Rhino automatically sets the trail for activities created within event handlers for activities that have a trail attached. To disable tracing of outgoing HTTP requests in this scenario, the SAS trail on the OutgoingRestActivity must be explicitly set to null.

See the Rhino SAS Facility documentation for the details of this automatic trail handling behaviour.

SAS events

The REST resource adaptor raises a SAS marker for trail correlation with other SAS-enabled products when recording a request or response message. It raises a GENERIC_CORRELATOR_MARKER containing the X-Span-ID HTTP header, if it is set.

The following SAS event mini-bundles define all the SAS event that are recorded by a Unified REST resource adaptor.

SAS Events at the HTTP layer

HTTP layer SAS events mini-bundle
---
version: 1.0
events:
  INCOMING_MESSAGE:
    summary: 'Received HTTP {{ var_data[3] }} from {{ var_data[2] }}'
    details: | Received HTTP message from {{ var_data[2] }}:{{ static_data[1] }} on {{ var_data[1] }}:{{ static_data[0] }} <sas:fixed-width-font>{{ var_data[0] }}</sas:fixed-width-font>
    level: 60
    call_flow:
      caption: '{{ var_data[3] }}'
      data: '{{ var_data[0] }}'
      protocol: HTTP
      direction: in
      local_address: '{{ var_data[1] }}:{{ static_data[0] }}'
      remote_address: '{{ var_data[2] }}:{{ static_data[1] }}'
      message_id: '{{ var_data[4] }}'
      call_info_id: '{{ var_data[5] }}'
  OUTGOING_MESSAGE:
    summary: 'Sent HTTP {{ var_data[3] }} to {{ var_data[2] }}'
    details: | Sent HTTP message to {{ var_data[2] }}:{{ static_data[1] }} from {{ var_data[1] }}:{{ static_data[0] }} <sas:fixed-width-font>{{ var_data[0] }}</sas:fixed-width-font>
    level: 60
    call_flow:
      caption: '{{ var_data[3] }}'
      data: '{{ var_data[0] }}'
      protocol: HTTP
      direction: out
      local_address: '{{ var_data[1] }}:{{ static_data[0] }}'
      remote_address: '{{ var_data[2] }}:{{ static_data[1] }}'
      message_id: '{{ var_data[4] }}'
      call_info_id: '{{ var_data[5] }}'

SAS Events at the REST layer

Unified REST RA core SAS events mini-bundle
version: 1.0
events:

  # General

  NO_SERVICE_TO_HANDLE_REQUEST:
    level: 20
    summary: 'There is no service available to handle a REST request'
    details: | api: {{ var_data[0] }} operation: {{ var_data[1] }} event-type: {{ var_data[2] }}

  NO_SBB_PROCESSED_REQUEST:
    level: 20
    summary: 'No SBB processed a REST request'
    details: | event-type: {{ var_data[0] }}

  REQUEST_EVENT_FAILED_TO_BE_PROCESSED:
    level: 20
    summary: 'A REST request event failed to be processed'
    details: | event-type: {{ var_data[0] }}

  NO_SERVICE_TO_HANDLE_RESPONSE:
    level: 20
    summary: 'There is no service available to handle a REST response'
    details: | event-type: {{ var_data[0] }} status-code: {{ var_data[1] }}

  NO_SBB_PROCESSED_RESPONSE:
    level: 20
    summary: 'No SBB processed a REST request'
    details: | event-type: {{ var_data[0] }} status-code: {{ var_data[1] }} flags: {{ var_data[2] }} reason: {{ var_data[3] }}

  RESPONSE_EVENT_FAILED_TO_BE_PROCESSED:
    level: 20
    summary: 'A REST response event failed to be processed'
    details: | event-type: {{ var_data[0] }} flags: {{ var_data[1] }} reason: {{ var_data[2] }}

  UNKNOWN_REQUEST_EVENT:
    level: 20
    summary: 'Failed to deduce an event-id for an incoming request'
    details: | api: {{ var_data[0] }}

  UNKNOWN_RESPONSE_EVENT:
    level: 20
    summary: 'Failed to deduce an event-id for an incoming response'
    details: | api: {{ var_data[0] }}

  NO_AVAILABLE_REST_API:
    level: 20
    summary: 'There is no REST API available'
    details: | path: {{ var_data[0] }}
REST Ratype SPI SAS events mini-bundle
version: 1.0
events:

  # Client API request/response
  # Server API callback request/response

  SEND_REST_REQUEST:
    level: 60
    summary: 'Sending {{ static_data[0] | enum: "REST_MESSAGE_TYPE" }}'
    details: | messageType: {{ static_data[0] | enum: "MessageType" }} api: {{ var_data[0] }} operationId: {{ var_data[1] }} body: {{ var_data[2] | if_blank: "no body" }}

  RECEIVE_REST_RESPONSE:
    level: 60
    summary: 'Receiving {{ static_data[0] | enum: "REST_MESSAGE_TYPE" }}'
    details: | messageType: {{ static_data[0] | enum: "MessageType" }} api: {{ var_data[0] }} operationId: {{ var_data[1] }} status-code: {{ var_data[2] }} body: {{ var_data[3] | if_blank: "no body" }}

  # Server API request/response
  # Client API callback request/response

  RECEIVE_REST_REQUEST:
    level: 60
    summary: 'Receiving {{ static_data[0] | enum: "REST_MESSAGE_TYPE" }}'
    details: | messageType: {{ static_data[0] | enum: "MessageType" }} api: {{ var_data[0] }} operationId: {{ var_data[1] }} body: {{ var_data[2] | if_blank: "no body" }}

  SEND_REST_RESPONSE:
    level: 60
    summary: 'Sending {{ static_data[0] | enum: "REST_MESSAGE_TYPE" }}'
    details: | messageType: {{ static_data[0] | enum: "MessageType" }} api: {{ var_data[0] }} operationId: {{ var_data[1] }} status-code: {{ var_data[2] }} body: {{ var_data[3] | if_blank: "no body" }}

enums:

  REST_MESSAGE_TYPE:
    1: 'REST Request'
    2: 'REST Response'
    3: 'Callback REST Request'
    4: 'Callback REST Response'

Testing

There are many testing tools available that support testing REST APIs. On this page two approaches to testing are demonstrated by testing the PingPong API.

Testing Ping Pong APU with Metaswitch Scenario Simulator

The Scenario Simulator is a testing tool which simulates network traffic — for testing network elements and services. You define your test cases with scenario definition files, which describe the message flows between network elements. The scenario simulator can play one or more roles when you run the scenario.

Note The REST API Framework Demonstration uses the scenario simulator.

The following scenario file describes a call-flow where the client sends a Ping (api=SS-ClientApiToTest, scenario=SS-ClientApiTestScenarioToRun) request to a server. The expected response is a 200 OK, with a response body of Pong!.

scenario sim file
ping-request  [description "Triggers the example service with a pingpong api POST rest request."]  (FORMAT 1.0) {
   (ROLES) {
    RESTCLIENT  [description "client to the unified rest ra"] ;
    UNIFIEDREST  [description "rhino with a unified rest ra installed and activated"] ;
  }
   (DIALOGS) {
    RESTCLIENT-UNIFIEDREST  [description "rest/http interactions"]  (ROLE_A RESTCLIENT, ROLE_B UNIFIEDREST, SCHEMA http, VERSION 1.0c) {
      applicationContext "HTTP/1.1";
    }
  }
   (TABLES) ;
  POST  (DIALOG RESTCLIENT-UNIFIEDREST, DIRECTION A_TO_B) {
    Request-URI {
      Encoded "/pingpong/ping";
    }
    headers {
      Content-Length  (AUTO) ;
      Content-Type "application/json";
      Host "localhost:8000";
    }
    Message-Body {
      application_json_UTF-8 {
        Object {
          Name-Value {
            Name "api";
            Value {
              Primitive {
                String "SS-ClientApiToTest";
              }
            }
          }
          Name-Value {
            Name "scenario";
            Value {
              Primitive {
                String "SS-ClientApiTestScenarioToRun";
              }
            }
          }
        }
      }
    }
  }
  200  (DIALOG RESTCLIENT-UNIFIEDREST, DIRECTION B_TO_A) {
    headers {
      Content-Length  (AUTO) ;
      Date  (AUTO) ;
    }
    Message-Body {
      text_plain_UTF-8 "Pong!";
    }
  }
}
Tip View the scenario file with the Scenario Editor.

The scenario simulator can be used interactively to run scenarios.

running the scenario sim
~/work/unified-rest-ra-sdk/sim$ ~/scenario-sim/scenario-simulator.sh -f setup-restclient.commands 1
Starting JVM...
Processing commands from file at setup-restclient.commands
Processing command: set-endpoint-address rest-client-http-endpoint localhost:8181
Processing command: create-local-endpoint rest-client-http-endpoint http -propsfile setup-restclient.properties
Initializing local endpoint "rest-client-http-endpoint" ...
Local endpoint initialized.
Processing command: bind-role RESTCLIENT rest-client-http-endpoint
Processing command: set-endpoint-address unified-rest-http-endpoint localhost:8000
Processing command: bind-role UNIFIEDREST unified-rest-http-endpoint
Finished reading commands from file
Ready to start

Please type commands... (type "help" <ENTER> for command help)
> load-scenario ping-request.scen 2
Playing role "RESTCLIENT" in initiating scenario "ping-request" with dialogs [RESTCLIENT-UNIFIEDREST]
> run-session ping-request 3
Send -->  POST to unified-rest-http-endpoint
Recv <--  200 OK from unified-rest-http-endpoint
Outcome of "ping-request" session: Matched scenario definition "ping-request"
>
  1. Start the scenario simulator. The argument is a file of commands to run on startup that defines all the roles involved in the scenario.

  2. Load a scenario to run

  3. Run the scenario and observe the results. The scenario simaulor plays the role of the client and sends a POST request to the server (the example-rest-ra in Rhino). The server (the service running in Rhino) sends a 200 OK response. This response matches the expected response in this scenario, so the test passes.

Tip
The scenario sim setup commands file used in this test
set-endpoint-address rest-client-http-endpoint localhost:8181 1
create-local-endpoint rest-client-http-endpoint http -propsfile setup-restclient.properties
bind-role RESTCLIENT rest-client-http-endpoint 2

set-endpoint-address unified-rest-http-endpoint localhost:8000 3
bind-role UNIFIEDREST unified-rest-http-endpoint 4
  1. The endpoint of the client

  2. The ROLE in the scenario that corresponds to the client (the simulator will play this role)

  3. The endpoint of the server

  4. The ROLE in the scenario that corresponds to the server (the example-rest-ra in Rhino)

Here is a snippet of the Rhino logs showing what happened on the server.

rhino logs
[example_rest_ra.controller] incomingHttpRequest: id=HttpRequestId[incoming,6,3], request=FullHttpRequest[POST http://localhost:8000/pingpong/ping] 1

[example_rest_ra.controller] Select an API suitable for: '/pingpong/ping' 2
[example_rest_ra] startActivity: HttpActivity[nodeID=101,requestId=HttpRequestId[incoming,6,3]]

[example_rest_ra] selectApiRestRATypeProvider('/pingpong') = 'PingpongApiServerPluginRATypeProvider@6a3877a1' 3

[example_rest_ra] fireEvent: ah=HttpActivity[nodeID=101,requestId=HttpRequestId[incoming,6,3]], eventID=EventTypeID[name=PingRequest,vendor=ExampleCo,version=1.0], event=PingRequest[pingContext=PingContext { api: SS-ClientApiToTest, scenario: SS-ClientApiTestScenarioToRun }], address=null, flags=96 4

[example-rest-api-sbb] sbbCreate 5
[example-rest-api-sbb] sbbPostCreate
[example-pingpong-api-sbbpart] SBB part created

[example-pingpong-api-sbbpart] Received: PingRequest[pingContext=PingContext { api: SS-ClientApiToTest, scenario: SS-ClientApiTestScenarioToRun }] 6
[example-pingpong-api-sbbpart] Ping! PingRequest[pingContext=PingContext { api: SS-ClientApiToTest, scenario: SS-ClientApiTestScenarioToRun }]

[example-rest-api-sbb] Triggering: api=SS-ClientApiToTest, scenario=SS-ClientApiTestScenarioToRun 7

[example_rest_ra] sendResponse: Event: SEND_REST_RESPONSE, Type: REST_Response, API: PINGPONG, Operation: Ping, Content: 8
[example-rest-api-sbb] sbbStore

[example_rest_ra] eventProcessingSuccessful: ah=HttpActivity[nodeID=101,requestId=HttpRequestId[incoming,6,3]], eventID=EventTypeID[name=PingRequest,vendor=ExampleCo,version=1.0], event=PingRequest[pingContext=PingContext { api: SS-ClientApiToTest, scenario: SS-ClientApiTestScenarioToRun }], address=null, flags=256 9

[example-rest-api-sbb] sbbRemove 10
  1. The example-rest-ra receives the POST REST request

  2. The example-rest-ra determines which API should process this new request

  3. …​ It is the PingpongApiServerPluginRATypeProvider

  4. The example-rest-ra creates, and fires an event to Rhino for processing by a service

  5. example-rest-api-sbb (the root sbb of the example service) and example-pingpong-api-sbbpart are instantiated

  6. The example-pingpong-api-sbbpart instance receives the PingRequest event.

  7. …​ and triggers the example-rest-api-sbb, which logs some details about the Ping request.

  8. The example-pingpong-api-sbbpart sends a 200 OK response

  9. Rhino notifies the example-rest-ra that the PingRequest event has been processed by a service.

  10. Rhino cleans up the example-rest-api-sbb instance

Testing Ping Pong API the with Swagger Inspector

In the following screenshot, Swagger Inspector is used to trigger the example service running in Rhino. In this case, the Swagger Inspector is playing the role of the client.

pingpong swagger inspector
Using swagger inspector

Here is a snippet of the Rhino logs showing what happened on the server.

rhino logs
[example-rest-api-sbb] sbbCreate 1
[example-rest-api-sbb] sbbPostCreate
[example-pingpong-api-sbbpart] SBB part created
[example-pingpong-api-sbbpart] Received: PingRequest[pingContext=PingContext { api: ClientApiToTest, scenario: ClientApiScenario }] 2
[example-pingpong-api-sbbpart] Ping! PingRequest[pingContext=PingContext { api: ClientApiToTest, scenario: ClientApiScenario }]
[example-rest-api-sbb] Triggering: api=ClientApiToTest, scenario=ClientApiScenario 3
[example-rest-api-sbb] sbbStore
[example-rest-api-sbb] sbbRemove 4
  1. example-rest-api-sbb (the root sbb of the example service) and example-pingpong-api-sbbpart are instantiated

  2. The example-pingpong-api-sbbpart instance receives the PingRequest event.

  3. …​ and triggers the example-rest-api-sbb, which logs some details about the Ping request.

  4. Rhino cleans up the example-rest-api-sbb instance

Testing Pet Store API with the Swagger Inspector

In the following screenshot, Swagger Inspector is used to trigger the example service running in Rhino. In this case, the Swagger Inspector is playing the role of the client.

petstore list pets 1
Listing the Pets in the Pet Store
petstore create pet
Add a new Pet to the Store
rhino logs - listing the pets in the store
 [trace.example-rest-api-sbb] sbbCreate
 [trace.example-rest-api-sbb] sbbPostCreate
 [trace.example-petstore-api-sbbpart] SBB part created
 [trace.example-pingpong-api-sbbpart] SBB part created
 [trace.example-petstore-api-sbbpart] Received: CreatePetRequest[pet=Pet { id: 10, name: Ploppy }]
 [trace.example-petstore-api-sbbpart] Added pet: Pet { id: 10, name: Ploppy }
 [trace.example-rest-api-sbb] sbbStore
 [trace.example-rest-api-sbb] sbbRemove
petstore list pets 2
Listing the updated Pets in the Pet Store
rhino logs - listing the pets in the store
[trace.example-rest-api-sbb] sbbCreate
[trace.example-rest-api-sbb] sbbPostCreate
[trace.example-petstore-api-sbbpart] SBB part created
[trace.example-pingpong-api-sbbpart] SBB part created
[trace.example-petstore-api-sbbpart] Received: ListPetsRequest[]
[trace.example-petstore-api-sbbpart] Listing Pets: [Pet { id: 1, name: Stripey }, Pet { id: 2, name: Splodge }, Pet { id: 3, name: Simba }, Pet { id: 10, name: Ploppy }]
[trace.example-rest-api-sbb] sbbStore
[trace.example-rest-api-sbb] sbbRemove
petstore list unknown pet
Find an unknown Pet in the Store
rhino logs - listing the pets in the store
[trace.example-rest-api-sbb] sbbCreate
[trace.example-rest-api-sbb] sbbPostCreate
[trace.example-petstore-api-sbbpart] SBB part created
[trace.example-pingpong-api-sbbpart] SBB part created
[trace.example-petstore-api-sbbpart] Received: ShowPetByIdRequest[petId=5]
[trace.example-petstore-api-sbbpart] Show pet with id: 5 = null
[trace.example-rest-api-sbb] sbbStore
[trace.example-rest-api-sbb] sbbRemove
petstore list pet 10
Find a Pet in the Store
rhino logs - listing the pets in the store
[trace.example-rest-api-sbb] sbbCreate
[trace.example-rest-api-sbb] sbbPostCreate
[trace.example-petstore-api-sbbpart] SBB part created
[trace.example-pingpong-api-sbbpart] SBB part created
[trace.example-petstore-api-sbbpart] Received: ShowPetByIdRequest[petId=10]
[trace.example-rest-api-sbb] sbbStore
[trace.example-rest-api-sbb] sbbRemove

Ping Pong API

The Ping-Pong API is used to test client APIs running in Rhino.

A test suite initiates a test scenario by sending Rhino a Ping REST request, with a request body that identifies the client API, and associated test scenario, the Rhino test service should trigger.

This user guide will use the Ping Pong API as an illustrative example while explaining how to create a REST API and REST resource adaptor with the Rhino REST API Framework.

pingpong api.drawio
Using the Ping Pong API

Ping Pong API specification

Preamble

Preamble
openapi: "3.0.0"
info:
  version: 1.0.0
  title: Ping Pong API
  description: | The Ping Pong API is used to test client APIs running in Rhino. The test suite initiates a test scenario by sending Rhino a Ping REST request, with a request body that identifies the client API and associated test scenario the Rhino test service should trigger.
  license:
    name: Proprietary
    url: 'https://www.metaswitch.com'
servers:
  - url: /pingpong

Ping request

A GET request with request URL path /pingpong/ping. Request body is a JSon object defined by #/components/schemas/PingContext

Ping request
  /ping:
    post:
      operationId: ping
      tags:
        - ping
      requestBody:
        description: a test scenario to triger for an api
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PingContext'

Ping responses

Defines the set of possible responses. 200 Success response, with a String response body.

The default corresponds to any other response. The response body is a JSon object defined by #/components/schemas/Error.

Ping responses
      responses:
        '200':
          description: OK
          content:
            text/plain:
              schema:
                type: string
                example: OK
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

Ping context

The Ping request body is a JSon object with two required fields:

  • api — a String that identifies the API to test

  • scenario — a String that identifies the test scenario to trigger

Ping context
components:
  schemas:

    PingContext:
      type: object
      required:
        - api
        - scenario
      properties:
        api:
          type: string
        scenario:
          type: string

Pet Store API

The Pet Store API is a common REST API used in REST literature.

petstore api.drawio
Implementing a Petstore in Rhino TAS

Pet Store API specification

Preamble

Preamble
openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: /petstore/v1

List Pets request

A GET request with request URL path /petstore/v1/pets.

List Pets request
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      tags:
        - pets
      parameters:
        - name: limit
          in: query
          description: How many items to return at one time (max 100)
          required: false
          schema:
            type: integer
            format: int32

List Pet responses

Defines the set of possible responses. 200 Success response returns a list of pets. The response body is a JSon object defined by #/components/schemas/Pets

The default corresponds to any other response. The response body is a JSon object defined by #/components/schemas/Error.

List Pets request responses
      responses:
        '200':
          description: A paged array of pets
          headers:
            x-next:
              description: A link to the next page of responses
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pets"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

Create Pet request

A POST request with request URL path /petstore/v1/pets. The request body is a JSon object defined by #/components/schemas/Pet

List Pets request
    post:
      summary: Create a pet
      operationId: createPet
      requestBody:
        description: Pet to add to the store
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Pet'
      tags:
        - pets
      responses:
        '204':
          description: Null response
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

List Pet by ID request

A GET request with request URL path /petstore/v1/pets/{petid}.

List Pet by ID request
  /pets/{petId}:
    get:
      summary: Info for a specific pet
      operationId: showPetById
      tags:
        - pets
      parameters:
        - name: petId
          in: path
          required: true
          description: The id of the pet to retrieve
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: Expected response to a valid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

Pet and Pets

Defines JSon objects for a Pet and a collection of Pets.

Pet
    Pet:
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string
Pets
    Pets:
      type: array
      items:
        $ref: "#/components/schemas/Pet"
    Error:
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string