Introduction

The Rhino REST API Framework generates SLEE RA Types from OpenAPI documents. Different OpenAPI documents might produce very different RA Types — this section describes the common structure shared by generated RA Types, and how they are used from an SBB.

For concrete examples we will use a test pet store API from the openapi-generator source code.

The framework can generate an RA Type for a client or server role, or an RA Type that supports both roles. Often an application will only be ever be acting in one role. For example if using an API that is hosted externally, the application does not need to be a server, unless for testing purposes. When creating an API Module, the SDK user can select the roles they need.

Tip Read JAIN SLEE (JSR 240) to learn more about Resource Adaptors and Resource Adaptor Types

Events

SLEE Events are how SBBs receive input from the outside world. Each REST operation defined in an OpenAPI document will generate SLEE event type definitions for the request and its defined responses. An SBB can then listen for events it is interested in.

Request Events

In a server RA Type, each REST operation maps to a distinct SLEE event type. The event name is derived from the OpenAPI operation’s name. The event vendor and version are specified in the API Module’s api.properties file. The framework generates an event class (a Java class) containing fields for each parameter in the operation’s OpenAPI definition.

For example, consider this getPetById operation from the example API:

'/pet/{petId}':
  get:
    tags:
      - pet
    summary: Find pet by ID
    description: Returns a single pet
    operationId: getPetById
    parameters:
      - name: petId
        in: path
        description: ID of pet to return
        required: true
        schema:
          type: integer
          format: int64
    responses:
      '200':
        description: successful operation
        content:
          application/xml:
            schema:
              $ref: '#/components/schemas/Pet'
          application/json:
            schema:
              $ref: '#/components/schemas/Pet'
      '400':
        description: Invalid ID supplied
      '404':
        description: Pet not found
    security:
      - api_key: []

This generates an event class like this:

AddPetRequest.java
@SleeEvent(id = @ComponentId(name = "com.example.petstore.api.GetPetByIdRequest",
                             vendor = "Example", version = "1.0"))
public class GetPetByIdRequest {

    public Long getPetId() { ... }

    public RestResponseBuilder create_200_SuccessResponse(String contentTypeStr, Pet responseBody) { ... }

    public RestResponseBuilder create_400_ClientErrorResponse() { ... }

    public RestResponseBuilder create_404_ClientErrorResponse() { ... }

    ...
}

The SBB developer adds event handler methods for the events they need to implement, for example:

Event handler method
@EventMethod(eventType = @ComponentId(name = "com.example.petstore.api.GetPetByIdRequest",
             vendor = "Example", version = "1.0"), initialEvent = true)
public void onGetPetByIdRequest(GetPetByIdRequest event, ActivityContextInterface aci) {
    Pet pet = storage.lookupPetById(event.getPetId());
    ...
}

Response Events

In a client RA Type, each response defined on a REST operation maps to a distinct SLEE event type.

Continuing the addPet example from above, the response events generated for a client RA Type look like this:

AddPet_200_SuccessResponse.java
package com.example.petstore.api;

@SleeEvent(id = @ComponentId(name = "com.example.openapi.petstore.api.AddPet_200_SuccessResponse",
                             vendor = "Example",
                             version = "1.0.0")) 1
public class AddPet_200_SuccessResponse { 2

    public RestResponse getResponse() { ... } 3

    public int getStatusCode() { ... } 4

    @Nullable
    public Pet getContent() { ... } 5

    ...
}
1 The event class has the appropriate @SleeEvent annotation declaring the SLEE Event Type ID.
2 The event class name is the camel-cased operation name, concatenated with the response status code and the status code class, "Success".
3 The underlying RestResponse object can be accessed.
4 The integer status code is accessible.
5 If the OpenAPI response definition has a content definition, the response content can be accessed with the type-safe getContent() method.

Activities

All generated RA Types share the same activity types, defined in the rest-api-common SLEE library:

  • com.opencloud.slee.rest.common.IncomingRestActivity

  • com.opencloud.slee.rest.common.OutgoingRestActivity

These activities represent a single REST operation — a request and its response — from the client or server point of view.

When an SBB, acting as a REST client, calls a REST operation method, the REST RA creates an OutgoingRestActivity object. The SBB attaches to this activity so that it can receive the REST response event. When the response event is processed, the activity automatically ends.

Each incoming request automatically creates a new IncomingRestActivity object in the REST RA, and the corresponding request event is fired on this activity. This is an initial event, in SLEE terms. The SBB receiving this event can generate a response from this activity object using IncomingRestActivity.createResponse(). Sending the response automatically ends the activity.

All REST operations are simple request/response pairs, which is why the generated RA Types can share the common activity types. There is no benefit to generating separate activity types for each RA Type.

Any more complicated behaviour, such as sessions that span multiple requests, must be implemented at the application level.

Resource Adaptor Interface

Each generated RA Type has a "provider" interface that is the SBB’s main interface to the Unified REST RA entity running in the SLEE. Like all RAs, the provider interface is accessible in JNDI using the JNDI name that is bound to the RA entities' link name in the SBB deployment descriptor (or its SLEE annotations).

The provider interface contains a method for creating an "ApiClient" instance, which in turn provides access to the methods for the API operations. The ApiClient instance can be configured for different situations, for example using a different URL to reach the API server.

If a REST RA supports several RA Types, each has its own provider interface that can be accessed independently. Multiple RA Types can be used in the same SBB or in separate, unrelated SBBs. This is just a part of the SLEE model.

Provider Interface

Below is an example of a generated provider interface. All generated RA Types will have a similar provider interface, just the type names change.

PetstoreProvider.java
package com.example.petstore;

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

The ApiConfiguration type is a configuration object defined in the rest-api-common library. The SBB passes in a default or custom ApiConfiguration in order to configure the ApiClient.

Below is an example of an SBB obtaining the provider and ApiClient in its setSbbContext() method (where this sort of initialization is normally done).

Obtaining a reference to the Client RA Interface
public abstract class PetstoreSbb implements Sbb {

    private PetstoreApiClient apiClient;

    @Override
    public void setSbbContext(SbbContext context) {
        try {
            Context env = (Context) new InitialContext().lookup("java:comp/env");
            PetstoreProvider provider = (PetStoreProvider) env.lookup("slee/resource/petstore");

            final ApiConfiguration petsApiConfig = ApiConfiguration.standardConfiguration();
            petsApiConfig.set(ApiOptions.destinationUrl, "https://api.petstore.example.com");

            this.apiClient = provider.getApiClient(petsApiConfig);

        } catch (NamingException e) {
            throw new RuntimeException(e);
        }
    }
    ...
}

Now that the SBB has an ApiClient object, it can start invoking operations using the API.

The separate provider and ApiClient interfaces are needed because the SLEE model guarantees there is only ever one instance of PetstoreProvider, corresponding to the REST RA entity.

The provider allows the SBB to create multiple ApiClient instances, with different configurations such as addresses or credentials. This means the same RA entity can be used by multiple services across a wide range of scenarios.

ApiClient Interface

The ApiClient interface is the SBB’s main entry point for invoking REST operations, as a client, or creating defined REST responses as a server. Readers familiar with OpenAPI may recognize this pattern from other clients generated by openapi-generator.

This interface provides access to various "Api" interfaces, corresponding to the tags in the OpenAPI definition. Tags in OpenAPI are how related operations are grouped together. Our example Pet Store API has the tags "pet", "store", and "user", generating an ApiClient interface like this:

ExampleApiClient.java
package com.example.petstore;

public interface ExampleApiClient {

    /**
     * Return an api object related to the PetApi API.
     * @return an PetApi instance.
     */
    PetApi getPetApi();

    /**
     * Return an api object related to the StoreApi API.
     * @return an StoreApi instance.
     */
    StoreApi getStoreApi();

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

And in the generated PetApi interface:

PetApi.java (client RA Type)
package com.example.petstore.api;

public interface PetApi {

    RestRequestBuilder createAddPetRequest(Pet pet); 1

    RestRequestBuilder createDeletePetRequest(Long petId, String apiKey);

    RestRequestBuilder createGetPetByIdRequest(Long petId);

    RestRequestBuilder createUpdatePetRequest(Pet pet);

    OutgoingRestActivity sendRequest(RestRequestBuilder requestBuilder) 2
            throws IOException;

    ...
}
1 The createXXXRequest() methods create a RestRequestBuilder object that is pre-populated with the parameters needed for the corresponding REST operation. Additional parameters and headers may be added if necessary.
2 The sendRequest() method sends the REST request to the target server configured in the ApiClient.

Operations without a tag will go into the DefaultApi interface.

The server-side PetApi interface has methods for creating the defined responses for each operation. Equivalent methods are available on the request event object as well for convenience.

PetApi.java (server RA Type)
package com.example.petstore.api;

public interface PetApi {

    RestResponseBuilder createAddPet_200_SuccessResponse(String contentTypeStr, Pet responseBody); 1

    RestResponseBuilder createGetPetById_200_SuccessResponse(String contentTypeStr, Pet responseBody);

    RestResponseBuilder createGetPetById_400_ClientErrorResponse();

    ...

    void sendResponse(RestResponseBuilder responseBuilder, ActivityContextInterface aci) 2
            throws IOException;
}
1 The createXXXResponse() methods create a RestResponseBuilder object that is pre-populated with the parameters needed for the corresponding REST response. Additional parameters and headers may be added if necessary.
2 The sendResponse() method sends the REST response to the client that sent the request. This is determined by the IncomingRestActivity ACI.
Previous page Next page