Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 

Disclaimer: This blog post is only applicable for the SAP Cloud SDK version of at most 2.19.2. We plan to continuously migrate these blog posts into our List of Tutorials. Feel free to check out our updated Tutorials on the SAP Cloud SDK.


Note: This post is part of a series. For a complete overview visit the SAP Cloud SDK Overview.



We have already seen the SAP Cloud SDK providing capabilities for conveniently consuming OData and SOAP based API services. But your application may target a web service, which serves resources from an REST based endpoint, instead of OData or SOAP. If the REST based endpoint is well-defined, its specification can be declared as OpenAPI.

In this post you will learn how to use the SAP Cloud SDK together with a well-defined web service endpoint, driven by OpenAPI.

Contents



  • OpenAPI

    • RESTful API Description Languages

    • OpenAPI Specification

    • Features

    • Basic structure

    • Code generation



  • SAP Cloud SDK

    • Usage of HttpClient

      • Spring RestTemplate

      • OpenFeign

      • Google WebClient

      • Limitations and currently not-supported frameworks



    • SAP API Business Hub



  • Appendix


OpenAPI and SAP Cloud SDK


With the SAP Cloud SDK you can ease the application development by using the already implemented interface between SAP Cloud Platform and your software, e.g. multi-tenancy, destination with prepared connectivity and authorization handling - with support for both platforms, CloudFoundry and Neo.

We will describe how you can generate a type-safe OpenAPI client using standard open-source tools and use the client together with the SAP Cloud SDK, which handles the connectivity and authentication. But before we go into more depth on this topic, let's briefly recap our understanding of OpenAPI.

RESTful API Description Languages


Most developers have encountered REST (representational state transfer) based web services described with at least one of the popular description languages:

  • Web Services Description Language (WSDL)

  • RESTful API Modeling Language (RAML)

  • Open Data Protocol (OData)

  • OpenAPI

  • ...


RESTful API description languages are designed to provide a formal description of a web API that is useful both to a human and for automated machine processing. Its structured description can be used to generate accessible documentation for developers. Due to the tool-based formatting conventions, all generated files follow the same approved pattern. For the developer, this is easier to read than free-form documentation, often encountered in the business.

OpenAPI Specification


The OpenAPI specification is originally known as the Swagger Specification. The name change was introduced with version 3.0. It describes the machine-readable interface for characterizing, producing, consuming, and visualizing RESTful web services. Some tools are based on the older specification, Swagger, and others already migrated to the OpenAPI framework. The proposed tools in this post can be found for either framework.

With the frameworks themselves being language-agnostic, you can find tools for almost any programming language available. The work of all tools is based on the provided interface file, which acts as an API description format for your REST API. Given the interface file, you can use the various tools to generate code, automate documentation and prepare test cases.

One of the most used tools are code generation and interactive online documentation, e.g. Swagger UI. In this blog post, we will particularly look at code generation based on OpenAPI specifications.

Additional Features:

  • With the declarative OpenAPI specification, clients can understand and consume services even without having the knowledge of or access to the backend implementation.

  • It’s possible to have the OpenAPI interface files being audited, for convention checks or security vulnerabilities.

  • The interface file allows creating a description for the API, including:

    • Available endpoints (/users) and operations on each endpoint (GET /users, POST /users)

    • Operation parameters as input and output for each operation

    • Authentication methods

    • Contact information, license and terms of use



  • The API specifications can be written in YAML or JSON. Both formats are easy to learn, read and interpret.


Basic structure


In this guide, we only use YAML examples to demonstrate the OpenAPI interface, but JSON works just equally well. A sample OpenAPI 3.0 definition looks like the following.
openapi: 3.0.0
info:
title: Sample API
description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML.
version: 0.1.9
servers:
- url: http://api.example.com/v1
description: Optional server description, e.g. Main (production) server
- url: http://staging-api.example.com
description: Optional server description, e.g. Internal staging server for testing
paths:
/users:
get:
summary: Returns a list of users.
description: Optional extended description in CommonMark or HTML.
responses:
'200': # status code
description: A JSON array of user names
content:
application/json:
schema:
type: array
items:
type: string

Note: In our use case of consuming a service, we expect the OpenAPI file as above to be downloadable from the service host. We do not expect to write or edit such files ourselves. This example merely serves as illustration to reference the general structure of the file.

The structure is defined in several sections:

  • At the beginning of the file, we find the metadata:

    • Every API definition must include the version of the OpenAPI Specification that this definition is based on, openapi: 3.0.0

    • Compound info section, which contains API information like title, version and description (optional)



  • servers specifies the API servers with base URL and optional description

  • paths defines individual endpoints of in your API, and the HTTP methods (operations) supported by these endpoints. An operation definition includes parameters (if any), request body (if any), possible response status codes (such as 200 OK or 404 Not Found) and response contents.

  • In addition, the OpenAPI specification supports the following authentication methods:

    • HTTP authentication: Basic, Bearer, and so on.

    • API key as a header or query parameter or in cookies

    • OAuth 2

    • OpenID Connect Discovery




Code generation


By using the OpenAPI generator you can easily create executable code from an OpenAPI file. Java is only one of the various supported languages to generate source code to. As our guide focuses on application development with Java version 8, we highly recommend running the generator as convenient Maven plugin.
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>RELEASE</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec>
<generatorName>java</generatorName>
<configOptions>
<java8>true</java8>
<dateLibrary>jave8</dateLibrary>
<library>resttemplate</library>
<sourceFolder>src/gen/java/main</sourceFolder>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>

Note: Change the wildcard RELEASE in plugin version to the latest version available in the Maven central repository. This will ensure consistency and reproducibility among multiple builds. Also change the path definition in inputSpecto your preferred api file. We highly recommend placing the OpenAPI file into the resources directory of your application.

In the plugin settings, notice the emphasize on the Java version and distinct value of library. This is our minimal setup recommendation to ensure library support with SAP Cloud SDK. Of course, there are many other parameters which can be set, to customize the generated code. Please find the official documentationonline.

During the project build process, the generator is run with the default Maven lifecycle phase generate-sources. In return, the generated code will vanish during the phase clean. Hence by running the full build routine, a fresh code generation is triggered automatically:
mvn clean install

SAP Cloud SDK


The SAPCloud SDK simplifies application development, as it provides out-of-the-box capabilities, such as abstractions of the underlying cloud platform implementation of SAP Cloud Platform, fault-tolerance and cache management. You will find many tutorials, project templates, and courses.

The library, which can be easily retrieved from the public Maven central, can be introduced to any java project. To utilize its main features, all it takes is creating the request based HttpClient and propagate it to the framework which calls an OpenAPI.

The prepared HttpClient already contains proxy settings and authentication headers, automatically resolved from the platform abstraction:
// create a new HttpClient for SCP destination called: MyDestination
final String DESTINATION_NAME = "MyDestination";
final HttpClient httpClient = HttpClientAccessor.getHttpClient(DESTINATION_NAME);

// ... manually run a HttpRequest with the instance

You can either call the REST endpoints yourself by taking the HttpClient on hand like above, or by using one of the libraries, suggested by the OpenAPI code generator.

Use the HttpClient for libraries suggested by the OpenAPI framework


For the following code examples, the HttpClient prepared by Cloud SDK needs to be injected to the automatically generated ApiClient framework. With the generated object you can query all API actions as described in the OpenAPI interface.

All library templates not only require an adjustment of nested REST library to feature the HttpClient, but also a modification of ApiClient. Here the base URL of the called REST service must be set at runtime, to ensure proper multi-tenancy and compliance of the registered destination.

Spring RestTemplate (recommended)


The ApiClient is changed to feature the correct destination-dependent endpoint path. For authorization and destination-dependent server URL, the instance of RestTemplate is created. The HttpClient is injected to the latter object.
public ApiClient createApiClient() throws URISyntaxException
{
// resolve destination
final Destination destination = DestinationAccessor.getDestination(DESTINATION_NAME);

// instantiate RestTemplate and ApiClient
final RestTemplate restTemplate = createRestTemplate();
final ApiClient apiClient = new ApiClient(restTemplate);

// set root of API Client base path
final URI uri = destination.getUri();
final URI path = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null);
apiClient.setBasePath(path.toString());
return apiClient;
}

public RestTemplate createRestTemplate()
{
// create new HttpClient for destination
final HttpClient httpClient = HttpClientAccessor.getHttpClient(DESTINATION_NAME);

// instantiate template with prepared HttpClient, featuring repeated response reading
final RestTemplate restTemplate = new RestTemplate();
final HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpRequestFactory.setHttpClient(httpClient);
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(httpRequestFactory));
return restTemplate;
}

 

OpenFeign


The HttpClient is injected to the Feign Builder by using the client modifier function of Feign Builder. As opposed to the other libraries, Feign works with interfaces, hence there is no implementation required for ApiClient.
public Feign.Builder createFeignBuilder() {
final HttpClient httpClient = HttpClientAccessor.getHttpClient(DESTINATION_NAME);

return Feign.builder()
.encoder(new FormEncoder(new JacksonEncoder(getObjectMapper())))
.decoder(new JacksonDecoder(getObjectMapper()))
.logger(new Slf4jLogger())
.client(new ApacheHttpClient(httpClient));
}

public <T extends ApiClient.Api> T buildApi(Class<T> apiClass) {
// resolve destination
final Destination destination = DestinationAccessor.getDestination(DESTINATION_NAME);

// set root of API Client base path
final URI uri = destination.getUri();
final URI path = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null);

Feign.Builder feignBuilder = createFeignBuilder();
return feignBuilder.target(apiClass, path.toString());
}

Google API Client


A custom ApiClient is created, by applying service path and the wrapped HttpClient to the constructor.
public ApiClient createHttpClient() {
// set root of API Client base path
final Destination destination = DestinationAccessor.getDestination(DESTINATION);
final URI uri = destination.getUri();
final URI path = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null);

// set HttpClient
final HttpClient httpClient = HttpClientAccessor.getHttpClient(destination);
final ApacheHttpTransport apacheHttpTransport = new ApacheHttpTransport(httpClient);
final HttpRequestInitializer initializer = null; // optional credentials
final ObjectMapper objectMapper = null; // optional Jackson ObjectMapper
return new ApiClient(path.toString(), apacheHttpTransport, initializer, objectMapper);
}

Limitations and not yet supported frameworks


Since the SAP Cloud SDK is currently only supporting the synchronous HttpClient of Apache as interface for web requests, some frameworks may not work with injecting the client:

  • OkHttp

  • Jersey

  • VertX

  • WebClient


If you want to use the Cloud SDK feature set but you have one of the unsupported library templates in place, you should consider migrating to a supported template instead. The template can be easily changed by modifying the code generator plugin configuration. Due to the consistent architecture of the generated code, a switch of the library template can be achieved with little effort. We recommend the plugin settings for code generation as described above.

SAP API Business Hub


The set of APIs officially supported by SAP solutions can be browsed in the SAP API Business Hub. Its services are declared as either ODATA, SOAP or REST. If you are interested in OData and SOAP, please take a look at what the Cloud SDK can provide as additional functionality.

In this guide, we focus on REST APIs. And if a service is labeled as such, we can expect a well maintained and downloadable OpenAPI 2.0 / Swagger file.



Each item can be clicked on. The "API reference" page will open. In here, you will find multiple documented endpoints and most of them are interactive. They can be accessed by the Swagger UI like interface and tested against a sandbox system:



The meta information about an API is stored in the "Details" page. Here you find the available URLs of the API, depending on your target landscape. Also details about authentication and links to in-depth documentation are referenced on this page.



Most importantly you will find the "Download Specification" button, which let's you download the OpenAPI file to the API. The specification can be downloaded as JSON or YAML. Both of them can be parsed by the code generator, as described above.

Please feel encouraged to experiment with the OpenAPI code generator and a common API from the SAP API Business Hub - like "Monitoring v2". Use the code generator settings as previously described above and try to inject the HttpClient of SAP Cloud SDK.

Appendix


Please find the following notes.

  • Broken letter casing in APIs provided by SAP API Business HubIf you encounter broken letter casing, bad HTTP method prefices (e.g. gETSomething), you can most likely fix the interface file. You can either do this manually by hand or automate this step as part of the Maven build, e.g.:
      <plugin>
    <groupId>com.google.code.maven-replacer-plugin</groupId>
    <artifactId>replacer</artifactId>
    <version>1.5.3</version>
    <executions>
    <execution>
    <phase>generate-sources</phase>
    <goals>
    <goal>replace</goal>
    </goals>
    </execution>
    </executions>
    <configuration>
    <file>${project.basedir}/src/main/resources/api.yaml</file>
    <outputFile>${project.basedir}/target/api.yaml.fixed</outputFile>
    <regex>true</regex>
    <replacements>
    <replacement>
    <token>operationId: GET(\w)</token>
    <value>operationId: get$1</value>
    </replacement>
    <replacement>
    <token>operationId: POST(\w)</token>
    <value>operationId: post$1</value>
    </replacement>
    <replacement>
    <token>operationId: PUT(\w)</token>
    <value>operationId: put$1</value>
    </replacement>
    <replacement>
    <token>operationId: DELETE(\w)</token>
    <value>operationId: delete$1</value>
    </replacement>
    <replacement>
    <token>operationId: OPTIONS(\w)</token>
    <value>operationId: options$1</value>
    </replacement>
    <replacement>
    <token>operationId: TRACE(\w)</token>
    <value>operationId: trace$1</value>
    </replacement>
    <replacement>
    <token>operationId: HEAD(\w)</token>
    <value>operationId: head$1</value>
    </replacement>
    <replacement>
    <token>operationId: CONNECT(\w)</token>
    <value>operationId: connect$1</value>
    </replacement>
    </replacements>
    </configuration>
    </plugin>​

    Note: The plugin configuration above does not overwrite the original api.yaml. Instead a new fixed copy is created as ./target/api.yaml.fixed. You will need to change your code generator plugin accordingly, such that it points to the fixed file.

7 Comments