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 — introduces the Rhino REST API Framework.
-
Installation and Setup — explains how to install and setup the Rhino REST API Framework.
-
Creating a REST API — explains how create REST APIs with the Rhino REST API Framework.
-
Creating a REST Resource Adaptor — explains how to create a REST resource adaptor with the Rhino REST API Framework.
-
Using REST APIs — explains how to use REST APIs in Rhino TAS - Telecom Application Server.
-
Service Assurance Server (SAS) Tracing — explains how to monitor a REST resource adaptor with SAS.
-
Deploying a REST Resource Adaptor — explains how to deploy a REST resource adaptor into Rhino TAS - Telecom Application Server.
-
Configuring the REST Resource Adaptor — explains the configuration properties of a REST resource adaptor.
-
Testing — introduction to testing REST APIs created with the Rhino REST API Framework.
-
Ping Pong API — introduces the Ping Pong REST API used as an example in the user guide.
-
Pet Store API — introduces the Pet Store REST API used as an example in the user guide.
Learn more about Rhino TAS - Telecom Application Server and JSLEE v1.1 specification (JSR 240). |
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.
Learn about the Rhino REST API Framework Architecture. |
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.1.0 or later.
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 Download the Rhino REST API Framework Uncompress the archive ~/work$ unzip unified-rest-ra-sdk-2.1.0.zip ~/work$ cd unified-rest-ra-sdk ~/work/unified-rest-ra-sdk$ ls -la total 52 drwxr-xr-x 6 sdkuser staff 4096 Nov 11 15:08 . drwx------ 5 sdkuser staff 4096 Nov 11 16:14 .. -rw-r--r-- 1 sdkuser staff 24 Nov 11 14:05 .build -rw-r--r-- 1 sdkuser staff 101 Nov 11 14:05 .gitignore -rw-r--r-- 1 sdkuser staff 21 Nov 11 14:05 .sdk.root -rw-r--r-- 1 sdkuser staff 267 Nov 11 14:10 README.txt drwxr-xr-x 5 sdkuser staff 4096 Nov 11 14:10 build -rw-r--r-- 1 sdkuser staff 400 Nov 11 14:05 build.xml -rw-r--r-- 1 sdkuser staff 293 Nov 11 14:05 deps.properties drwxrwxr-x 3 sdkuser staff 4096 Nov 11 15:08 repositories drwxr-xr-x 3 sdkuser staff 4096 Nov 11 14:10 rhino-sdk -rw-r--r-- 1 sdkuser staff 2004 Nov 11 14:10 sdk.properties drwxrwxr-x 2 sdkuser staff 4096 Nov 11 15:08 upstream-product-properties
|
||
---|---|---|---|
2 |
Update sdk.properties There are a few properties in
sdk.properties
Example values for an
ExampleCo Rhino REST API Framework project
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 ~/unified-rest-ra-sdk$ ant Buildfile: /home/sdkuser/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. ivy.cache.root=${sdk.root}/build/target/ivy-caches/offline-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=offline-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) Writing Ivy configuration to: /home/sdkuser/unified-rest-ra-sdk/ivy.properties [echo] Ivy Resolvers: /home/sdkuser/unified-rest-ra-sdk/build/ivy/offline-resolvers.xml [echo] Configuring Ivy with settings: /home/sdkuser/unified-rest-ra-sdk/build/ivy/ivysettings.xml [ivy:var] :: Apache Ivy 2.5.0-rc1 - 20180412005306 :: http://ant.apache.org/ivy/ :: [ivy:var] :: loading settings :: file = /home/sdkuser/unified-rest-ra-sdk/build/ivy/ivysettings.xml [ivy:resolve] :: loading settings :: file = /home/sdkuser/unified-rest-ra-sdk/build/ivy/ivysettings.xml [echo] Build infrastructure lib/ directory is missing or out of date. [echo] Populating lib/ from ivy... [mkdir] Created dir: /home/sdkuser/unified-rest-ra-sdk/build/target/lib [touch] Creating /home/sdkuser/unified-rest-ra-sdk/build/target/lib/.lib.uptodate ... The installation and setup is now complete.
|
Creating a REST API
A REST API module generates the artifacts related to a particular API such Events, Provider interfaces and so on.
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.
Use the
sdkadm list-modules command to list available module-packs.
|
4 |
Create an API module Create an API module with the
sdkadm create-module
|
---|---|
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
sdkadm top level module
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
|
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
|
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
|
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
|
10 |
The short name of the API, and the root java package for all generated java
API properties
|
The create-module
command will then create your new REST API module.
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:
~/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
drwxr-xr-x 7 fwuser staff 224 2 Nov 16:24 pingpong-api-server
drwxr-xr-x 8 fwuser staff 256 2 Nov 16:24 pingpong-api-server-plugin
drwxr-xr-x 7 fwuser staff 224 2 Nov 16:24 pingpong-api-server-sbbpart
-
This sub-module holds the API specification and a properties file. The other sub-modules have an ivy dependency on this module.
-
Generates a RA type that includes events, provider interfaces and so on for an RA type that can be deployed in Rhino
-
Generates a library component the Unified REST RA uses to load and drive the API
-
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.
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:
# 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.
Optional License for your API
One of the properties in the Using this property is optional. The default value means the API does not require a license. |
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.
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).
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.
Use the
sdkadm list-modules command to list available module-packs.
|
11 |
Create a REST RA module Create a REST RA module with the For example, to create a REST RA module called
sdkadm create-module
|
---|---|
12 |
Confirm the name of REST RA module The suggested value is the directory name passed in the
sdkadm top level module
|
The create-module
command will then create your new REST RA module.
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:
~/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
-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
-rw-r--r-- 1 fwuser staff 2924 3 Nov 13:53 ivy.xml
-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
-
Instructions for configuring the REST RA
-
directory with a configuration file that is updated when you add new REST APIs to the REST RA
-
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 For example, to add support for the PingPing API add:
|
||
---|---|---|---|
2 |
Update config/unified-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.
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.
@ResourceAdaptorDeployableUnit(
securityPermissions = @SecurityPermissions(
securityPermissionSpec = "grant {... };"
)
)
@ResourceAdaptor(
vendorExtensionID = "unified-rest-ra-id",
raTypes = {
@ResourceAdaptorTypeReference(
raType = @ComponentId(name="petstore-api-server", vendor="ExampleCo", version="1.0")),
@ResourceAdaptorTypeReference(
raType = @ComponentId(name="pingpong-api-server", vendor="ExampleCo", version="1.0")),
@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")),
@LibraryReference(
library = @ComponentId(name="pingpong-api-server-plugin", vendor="ExampleCo", version="1.0")),
@LibraryReference(
library = @ComponentId(name="rest-ratype-spi", vendor="OpenCloud", version="1.0.0"))
}
)
@OcAssociatedType(UnifiedRestFrameworkRA.class)
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.*;
-
@ResourceAdaptorTypeReference for the Pet Store API RA Type
-
@ResourceAdaptorTypeReference for the Ping Pong API RA Type
-
@LibraryReference for the Pet Store API plugin
-
@LibraryReference for the Ping Pong API plugin
-
@OcAssociatedType references the Unified REST RA core, where other remaining RA relatred annotations are defined
Using REST APIs
-
Service Visible Rhino REST API Framework API — discusses the Rhino REST API Framework APIs used in service development.
-
How to use generated sbbpart superclasses — Discuss, with an example, how to use an sbbpart superclass generated by Rhino REST API Framework.
-
How to write an SBB that uses a REST API — Discuss, with an example, how to write an SBB that uses a REST API generated by Rhino REST API Framework.
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.
-
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.
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.
Learn about the Rhino REST API Framework Architecture |
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:
Unresolved directive in rhino-rest-api-framework-users-guide/using-rest-apis/service-visible-framework-api.adoc - include::/mnt/volume-01/jenkins/workspace/product/ra/rest-api-framework/release-2.1.x/Docs/rest-api-framework-docs/rest-api-framework-public-docs/rhino-rest-api-framework-users-guide/include/PingPongApiServerSbbPart.java[tag=sbbpart-constructor]
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.
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.
Refer to the following examples: |
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.
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.
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 following NotificationsApi
interface, from a call notification REST API, would be used to create and send CallDirectionNotification
and CallEventNotification
requests.
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.
Creating and Sending responses
There are two methods for creating and sending responses:
-
Create the response by using an API object
-
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:
/**
* 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.
/**
* 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.
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.
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.
There are two aspects to this example:
-
A root SBB that defines an SBB local interface.
-
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.
Advantages of using the generated sbb-part superclass
|
Learn about Rhino TAS - Telecom Application Server and the JAIN SLEE (JSR 240). |
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()
.
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()
.
@SleeService(
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(
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",
vendor = "@component.vendor@",
ersion = "@component.version@"))
},
sbbClasses = @OcSBBClasses()
)
public abstract class ExampleRestApiSbb implements javax.slee.Sbb, TriggerScenario {
-
defines the service and identifies the root SBB
-
the example SBB (the root SBB for the service)
-
the sbb-part annotation links the root SBB to our Ping Pong sbb-part
@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
).
@LibraryReferences(
libraryRefs = {
@LibraryReference(library=@ComponentId(name="example-service-sbblocal",
vendor="@component.vendor@",
version="@component.version@"))
}
)
public class PingpongSbbpart extends PingpongApiServerSbbpart {
Implement sbb-part constructor
The PingpongSbbpart
calls the superclass constructor and creates a Ping
API object.
public PingpongSbbPart(SbbPartContext context) {
super(context);
this.pingApi = getApiProvider().getApiClient(
ApiConfiguration.standardConfiguration()).getPingApi();
}
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.
protected final void handlePingRequest(PingRequest request,
ActivityContextInterface aci,
EventContext eventContext) throws IOException
{
getTracer().finest("Ping! {}", request);
// defer to the root sbb; call triggerScenario() on its sbb-local interface
final PingContext context = request.getPingContext();
final TriggerScenario testsbb = (TriggerScenario) getSbbPartContext().getSbbLocalObject();
testsbb.triggerScenario(context.getApi(), context.getScenario());
// send a success response
RestResponseBuilder response = request.create_200_SuccessResponse("text/plain", "Pong!");
pingApi.sendResponse(response, aci);
}
protected final void rejectPingRequest(PingRequest request, ActivityContextInterface aci) {
try {
final RestResponseBuilder errorResponse = request.createDefaultResponse(
501,"application/json",
new Error().code(501)
.message("Failed to process Ping request: " + request.getPingContext()));
pingApi.sendResponse(errorResponse, aci);
}
catch (IOException e) {
getTracer().finest("Failed to send 501 error response", e);
}
}
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
@SBBPart(
id = @ComponentId(name = "@component.name@",
vendor = "@component.vendor@",
version = "@component.version@"),
sbbPartClasses = @SBBPartClasses(
sbbPartClass = @SBBPartClass(
className="@sbbpart.class.name@"
),
activityContextInterface = @SBBPartActivityContextInterface(interfaceName = "@sbbpart.class.aci@")
),
libraryRefs = {
@LibraryReference(library = @ComponentId(name = "@guava-library.name@",
vendor = "@guava-library.vendor@",
version = "@guava-library.version@")),
@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 = "pingpong-api-server",
vendor = "ExampleCo",
version = "1.0"),
resourceAdaptorObjectName
= PingpongApiServerSbbpart.pingpong_PROVIDER_JNDI_NAME,
resourceAdaptorEntityLink = "unified-rest-ra",
activityContextInterfaceFactoryName
= PingpongApiServerSbbpart.pingpong_ACI_FACTORY_JNDI_NAME
)
}
)
@SBBPartDeployableUnit(
securityPermissions =
@SecurityPermissions(securityPermissionSpec = "grant { permission java.security.AllPermission; };")
)
public class PingpongApiServerSbbpart {
public static final String pingpong_PROVIDER_JNDI_NAME = "api/pingpong-api-server/provider";
public static final String pingpong_ACI_FACTORY_JNDI_NAME = "api/pingpong-api-server/acifactory";
public PingpongApiServerSbbpart(SbbPartContext context) {
this.sbbPartContext = context;
this.tracer = (ExtendedTracer) sbbPartContext.getTracer(context.getSbbPart().getName());
try {
final Context env = (Context) new InitialContext().lookup("java:comp/env");
this.apiProvider = (PingpongApiServerProvider)
env.lookup(pingpong_PROVIDER_JNDI_NAME);
this.apiAciFactory = (PingpongApiServerACIFactory)
env.lookup(pingpong_ACI_FACTORY_JNDI_NAME);
}
catch (NamingException e) {
throw new RuntimeException("NamingException trying to locate PingpongApiServerProvider", e);
}
}
@EventMethod(
eventType = @ComponentId(name = "com.exampleco.openapi.pingpong.api.PingRequest",
vendor = "ExampleCo",
version = "1.0"),
initialEvent = true,
initialEventSelectVariable = IESVariableType.ActivityContext
)
public void onPingRequest(PingRequest request,
ActivityContextInterface aci,
EventContext eventContext)
{
// handle PingRequest event
tracer.finest("Received: {}", request);
try {
handlePingRequest(request, aci, eventContext);
}
catch(IOException e) {
tracer.finest("Failed to handle: {}", request, e);
rejectPingRequest(request, aci);
}
}
protected void handlePingRequest(PingRequest request,
ActivityContextInterface aci,
EventContext eventContext) throws IOException
{
throw new IOException("handlePingRequest() is not implemented.");
}
protected void rejectPingRequest(PingRequest request, ActivityContextInterface aci) {
tracer.info("rejectPingRequest() is not implemented.");
}
-
slee-annotation for the
PingRequest
event handler -
implementation of the
PingRequest
event handler -
developer overrides this method to define normal behaviour for handling a
PingRequest
-
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.
To develop the Pet Store SBB you must:
Learn about Rhino TAS - Telecom Application Server and the JAIN SLEE (JSR 240). |
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.
@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.
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.
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
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()
.
@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.
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.
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);
}
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.
@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);
}
}
@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 |
---|---|---|---|
|
|
|
Local IP address that the Unified REST resource adaptor will listen on for requests. |
|
|
|
Local TCP port that the Unified REST resource adaptor will listen on for requests. |
|
|
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. |
|
|
|
|
The maximum number of outgoing TCP connections the resource adaptor will open to a single host when sending requests to that host. |
|
|
|
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. |
|
|
|
The time (in ms) that the resource adaptor will keep an outgoing TCP connection open for, with no activity on the connection. |
|
|
|
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. |
|
|
|
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". |
|
|
|
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. |
|
|
|
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. |
|
|
|
The time (in ms) that the resource adaptor will keep an incoming TCP connection open for, with no activity on the connection. |
|
|
|
The default value of the Server header set in outgoing responses. Applications may override this by setting the Server header in the response manually. |
|
|
|
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. |
|
|
|
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 |
|
|
|
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. |
|
|
|
The number of workers used by the server and client worker executors. 0 means use Netty default: 2 * |
|
|
|
If enabled, the Unified REST resource adaptor will compress outgoing responses automatically, while respecting the |
|
|
|
If enabled, the Unified REST resource adaptor will decompress incoming responses automatically, while respecting the |
|
|
|
If enabled, the Unified REST resource adaptor will create SAS Trails for incoming requests, and report them automatically. |
|
|
|
The default URL the Unified REST resource adaptor will provide to services, to include as a callback URL that identifies the current cluster node. |
|
|
|
The default URL the Unified REST resource adaptor will provide to services, to include as a callback URL that identifies the cluster. |
|
|
Local IP address of the service mesh server, if present. If unset no service mesh will be used. If a service mesh is used, the message sent from the RA will be modified to be sent to the ServiceMesh address, with the original destination stored in the 'Host' header. Proxy and Service Mesh are mutually exclusive |
|
|
|
|
Local TCP port of the service mesh server, if present. If set to 0 no service mesh will be used. If a service mesh is used, the message sent from the RA will be modified to be sent to the ServiceMesh address, with the original destination stored in the 'Host' header. Proxy and Service Mesh are mutually exclusive. |
|
|
Local IP address or FQDN of the proxy server, if present. If unset no proxy will be used. Proxy and Service Mesh are mutually exclusive. |
|
|
|
|
Local TCP port of the proxy server, if present. If set to 0 no proxy will be used. Proxy and Service Mesh are mutually exclusive. |
|
|
|
Comma-separated list of regular expressions that will be matched against request URI paths when reporting incoming requests to SAS. Requests with matching URIs will have their content redacted in traces sent to SAS, to avoid sensitive information leaking via SAS. If empty then no redaction is performed. Partial matches are accepted, e.g. the pattern |
ThreadPoolSize , MaxChannelMemorySize and MaxTotalMemorySize are not currently used. In future, they will support configuration of the Netty OrderedMemoryAwareThreadPoolExecutor . |
Secure configuration
Name | Type | Default | Description |
---|---|---|---|
|
|
|
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. |
|
|
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. |
|
|
|
|
Path to JKS keystore file. System properties (such as |
|
|
|
Keystore password for file specified by KeyStore. |
|
|
|
Path to JKS keystore for trust certificates. The special value <<TRUST ALL>> may be used to bypass trust checks. |
|
|
|
Keystore password for file specified by TrustStore. |
|
|
|
List of cipher suites to pass to |
|
|
|
Value to pass to |
|
|
|
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 |
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.
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
~/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
~/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.
~/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]
[admin@localhost (#1)] listraentities
example-rest-ra
[admin@localhost (#2)] listraentityconfigproperties example-rest-ra
Configuration properties for resource adaptor entity example-rest-ra:
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
ServiceMeshAddress (java.lang.String):
ServiceMeshPort (java.lang.Integer): 0
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
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]
-
The resource adaptors installed (via
deploy-with-deps
) -
The resource adaptor entity that was created (via
configure-with-deps
) -
The configuration of the new resource adaptor entity
-
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.
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.
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
---
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] }}'
SENDING_MESSAGE:
summary: 'Sending HTTP {{ var_data[0] }} to {{ var_data[1] }}:{{ var_data[2] }}'
details: |
Attempting connection to {{ var_data[1] }}:{{ var_data[2] }}
level: 40
SENDING_MESSAGE_VIA_MESH:
summary: 'Sending HTTP {{ var_data[0] }} to {{ var_data[1] }} via service mesh at {{ var_data[2] }}:{{ var_data[3] }}'
details: |
Attempting connection to {{ var_data[1] }} via service mesh at {{ var_data[2] }}:{{ var_data[3] }}
level: 40
SAS Events at the REST layer
version: 1.0
events:
# General
# Not used, replaced with EVENT_FILTERED_NO_SERVICE_TO_HANDLE_REQUEST_EVENT
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] }}
# Not used, replaced with NO_SBB_PROCESSED_REST_REQUEST_EVENT
NO_SBB_PROCESSED_REQUEST:
level: 20
summary: 'No SBB processed a REST request'
details: |
event-type: {{ var_data[0] }}
# Not used, replaced with FAILED_TO_PROCESS_REST_REQUEST_EVENT
REQUEST_EVENT_FAILED_TO_BE_PROCESSED:
level: 20
summary: 'A REST request event failed to be processed'
details: |
event-type: {{ var_data[0] }}
# Not used, replaced with EVENT_FILTERED_NO_SERVICE_TO_HANDLE_RESPONSE_EVENT
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] }}
# Not used, replaced with NO_SBB_PROCESSED_REST_RESPONSE_EVENT
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] }}
# Not used, replaced with FAILED_TO_PROCESS_REST_RESPONSE_EVENT
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] }}
# security scheme related sas events
REQUEST_FAILED_SECURITY_CHECK:
level: 20
summary: 'A security scheme rejected an incoming request'
details: |
api: {{ var_data[0] }}
operation: {{ var_data[1] }}
security-scheme: {{ var_data[2] }}
required-scopes: {{ var_data[3] }}
SECURITY_SCHEME_COULD_NOT_BE_APPLIED_TO_REQUEST:
level: 20
summary: 'A security scheme could not be applied to an outgoing request'
details: |
api: {{ var_data[0] }}
operation: {{ var_data[1] }}
security-scheme: {{ var_data[2] }}
# New sas events used instead of the first six sas events
EVENT_FILTERED_NO_SERVICE_TO_HANDLE_REQUEST_EVENT:
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_REST_REQUEST_EVENT:
level: 20
summary: 'No SBB processed a REST request event'
details: |
event-type: {{ var_data[0] }}
FAILED_TO_PROCESS_REST_REQUEST_EVENT:
level: 20
summary: 'A REST request event failed to be processed'
details: |
event-type: {{ var_data[0] }}
flags: {{ var_data[1] }}
reason: {{ var_data[2] }}
EVENT_FILTERED_NO_SERVICE_TO_HANDLE_RESPONSE_EVENT:
level: 20
summary: 'There is no service available to handle a REST response'
details: |
api: {{ var_data[0] }}
operation: {{ var_data[1] }}
event-type: {{ var_data[2] }}
status-code: {{ var_data[3] }}
NO_SBB_PROCESSED_REST_RESPONSE_EVENT:
level: 20
summary: 'No SBB processed a REST response event'
details: |
event-type: {{ var_data[0] }}
status-code: {{ var_data[1] }}
FAILED_TO_PROCESS_REST_RESPONSE_EVENT:
level: 20
summary: 'A REST response event failed to be processed'
details: |
event-type: {{ var_data[0] }}
status-code: {{ var_data[1] }}
flags: {{ var_data[2] }}
reason: {{ var_data[3] }}
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: "REST_MESSAGE_TYPE" }}
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: "REST_MESSAGE_TYPE" }}
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: "REST_MESSAGE_TYPE" }}
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: "REST_MESSAGE_TYPE" }}
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.
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!
.
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!";
}
}
}
View the scenario file with the Scenario Editor. |
The scenario simulator can be used interactively to run scenarios.
~/work/unified-rest-ra-sdk/sim$ ~/scenario-sim/scenario-simulator.sh -f setup-restclient.commands
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
Playing role "RESTCLIENT" in initiating scenario "ping-request" with dialogs [RESTCLIENT-UNIFIEDREST]
> run-session ping-request
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"
>
-
Start the scenario simulator. The argument is a file of commands to run on startup that defines all the roles involved in the scenario.
-
Load a scenario to run
-
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.
The scenario sim setup commands file used in this test
|
Here is a snippet of the Rhino logs showing what happened on the server.
[example_rest_ra.controller] incomingHttpRequest: id=HttpRequestId[incoming,6,3], request=FullHttpRequest[POST http://localhost:8000/pingpong/ping]
[example_rest_ra.controller] Select an API suitable for: '/pingpong/ping'
[example_rest_ra] startActivity: HttpActivity[nodeID=101,requestId=HttpRequestId[incoming,6,3]]
[example_rest_ra] selectApiRestRATypeProvider('/pingpong') = 'PingpongApiServerPluginRATypeProvider@6a3877a1'
[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
[example-rest-api-sbb] sbbCreate
[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 }]
[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
[example_rest_ra] sendResponse: Event: SEND_REST_RESPONSE, Type: REST_Response, API: PINGPONG, Operation: Ping, Content:
[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
[example-rest-api-sbb] sbbRemove
-
The example-rest-ra receives the POST REST request
-
The example-rest-ra determines which API should process this new request
-
… It is the
PingpongApiServerPluginRATypeProvider
-
The example-rest-ra creates, and fires an event to Rhino for processing by a service
-
example-rest-api-sbb (the root sbb of the example service) and example-pingpong-api-sbbpart are instantiated
-
The example-pingpong-api-sbbpart instance receives the PingRequest event.
-
… and triggers the example-rest-api-sbb, which logs some details about the Ping request.
-
The example-pingpong-api-sbbpart sends a 200 OK response
-
Rhino notifies the example-rest-ra that the PingRequest event has been processed by a service.
-
Rhino cleans up the example-rest-api-sbb instance
Learn about Rhino TAS - Telecom Application Server and JAIN SLEE (JSR 240). |
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.
Here is a snippet of the Rhino logs showing what happened on the server.
[example-rest-api-sbb] sbbCreate
[example-rest-api-sbb] sbbPostCreate
[example-pingpong-api-sbbpart] SBB part created
[example-pingpong-api-sbbpart] Received: PingRequest[pingContext=PingContext { api: ClientApiToTest, scenario: ClientApiScenario }]
[example-pingpong-api-sbbpart] Ping! PingRequest[pingContext=PingContext { api: ClientApiToTest, scenario: ClientApiScenario }]
[example-rest-api-sbb] Triggering: api=ClientApiToTest, scenario=ClientApiScenario
[example-rest-api-sbb] sbbStore
[example-rest-api-sbb] sbbRemove
-
example-rest-api-sbb (the root sbb of the example service) and example-pingpong-api-sbbpart are instantiated
-
The example-pingpong-api-sbbpart instance receives the PingRequest event.
-
… and triggers the example-rest-api-sbb, which logs some details about the Ping request.
-
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.
[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
[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
[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
[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.
Ping Pong API specification
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:
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
.
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
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.
Pet Store API specification
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
.
/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
.
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
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}
.
/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:
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Pets:
type: array
items:
$ref: "#/components/schemas/Pet"
Error:
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string