Generate a Typed OpenAPI Client
The SAP Cloud SDK offers an OpenAPI client generator as a Maven plugin and as a command-line tool.
Either can be used to generate a client library for a REST API based on its OpenAPI specification.
The OpenAPI generator is a wrapper around the public open-source OpenAPI Generator where we have adjusted the mustache
templates to integrate the generated client library with the SAP Cloud SDK core.
The generator is designed to generate source code in english. You may also generate a client based on other languages in the OpenAPI specification. However, languages that use non-latin characters, specifically languages that read from right to left or that don't have capitalisation, may not be supported.
The generated Java classes need the following dependency to be present in your project:
<dependency>
<groupId>com.sap.cloud.sdk.datamodel</groupId>
<artifactId>openapi-core</artifactId>
</dependency>
To use the generated client library, either the SAP Cloud SDK BOM should be part of your project's <dependencyManagement>
section, or the version for the openapi-core
artifact must be mentioned explicitly here.
Generated Java Classes and Usage
In this section, we will explain how to use the OpenAPI Generator Maven Plugin.
A sample result can be seen in our open-source public repository with the SodaStore OpenAPI.
Given the sodastore.json
as input the following service classes get generated:
openapi-api-sample/
├── src/main/
│ ├── java/com/sap/cloud/sdk/datamodel/openapi/sample/
│ | ├── api/
│ | | ├── OrdersApi.java
│ | | └── SodasApi.java
│ | └── model/
│ | └── ...
│ └── resources/
│ └── sodastore.json
└── pom.xml
The generated API can be used like the following:
Destination destination;
// create the service with a destination
SodasApi service = new SodasApi(destination);
// create an entity object
SodaWithId soda = new SodaWithId().id(0L).name("Cola").brand("SAP-Cola").quantity(100).price(1.5f);
// execute request for entity update
service.sodasPut(soda);
Services can be instantiated in different ways:
new SodasApi( Destination )
new SodasApi( new ApiClient() )
new SodasApi( new ApiClient(RestTemplate) )
new SodasApi( new ApiClient(Destination) )
The ApiClient
allows for versatile customization.
Default service base-path, user-agent and request headers can be assigned.
When using Destination
these parameters are extracted automatically from the provided destination.
Alternatively, the RestTemplate
based constructor can be used for fully customized requests.
Using the OpenAPI Generator Maven Plugin
To use the Maven plugin following XML snippet should be added to the POM file of your project:
<plugin>
<groupId>com.sap.cloud.sdk.datamodel</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<!-- Maintain Maven property sap-cloud-sdk.version in your POM with the latest SAP Cloud SDK version -->
<version>${sap-cloud-sdk.version}</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<phase>generate-sources</phase>
</execution>
</executions>
<configuration>
<inputSpec>${project.basedir}/sample.yaml</inputSpec>
<outputDirectory>${project.basedir}/openapi</outputDirectory>
<apiPackage>com.mycompany.openapi.sample.api</apiPackage>
<modelPackage>com.mycompany.openapi.sample.model</modelPackage>
<apiMaturity>released</apiMaturity>
<!-- (Optional) You can add a custom copyright header:
<copyrightHeader>Copyright (c) this year, my company</copyrightHeader>
Or use the SAP copyright header:
<sapCopyrightHeader>true</sapCopyrightHeader>
-->
</configuration>
</plugin>
Maven requires that we specify the version for plugins explicitly.
In above code snippet you can see the version
tag pointing to the Maven property sap-cloud-sdk.version
.
Maintain such a property in your POM and ensure that you always use the latest SAP Cloud SDK version.
<properties>
<!-- Use latest version always as per https://sap.github.io/cloud-sdk/docs/java/release-notes -->
<sap-cloud-sdk.version>5.XX.X</sap-cloud-sdk.version>
</properties>
Maven will run the generator within the generate-sources
phase which is executed before compile
.
The phase generate-sources
is just a recommendation from our side, it can be changed per your project's requirement.
Refer to the Maven build lifecycle guide for a complete explanation.
Please note that the version of the above plugin must be specified at least once in your project, preferably in the root POM. Also, the version of the plugin should be the same as of the SAP Cloud SDK. We recommend using a Maven property for defining the version of both the Maven plugin and the SAP Cloud SDK BOM.
Using the Plugin from the Command Line
The maven plugin can also be invoked without a project from the command line using -D
parameter flags, for example:
mvn com.sap.cloud.sdk.datamodel:openapi-generator-maven-plugin:5.XX.X:generate -Dopenapi.generate.inputSpec=foo -Dopenapi.generate.outputDirectory=bar -Dopenapi.generate.apiPackage=api -Dopenapi.generate.modelPackage=model
See the full list of parameters here.
Generating Java Client Library for Multiple OpenAPI Specifications
This Maven plugin processes one OpenAPI specification per execution. In case you want to generate sources for multiple OpenAPI specifications then you need to create multiple executions of the plugin each corresponding to a particular OpenAPI specification. As an example refer to the plugin XML below:
<plugin>
<groupId>com.sap.cloud.sdk.datamodel</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<executions>
<execution>
<id>generate-sample1-id</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/sample1.yaml</inputSpec>
</configuration>
</execution>
<execution>
<id>generate-sample2-id</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/sample2.yaml</inputSpec>
</configuration>
</execution>
</executions>
<configuration>
<apiPackage>com.mycompany.openapi.sample.api</apiPackage>
<modelPackage>com.mycompany.openapi.sample.api.model</modelPackage>
<outputDirectory>${project.basedir}/openapi</outputDirectory>
<apiMaturity>released</apiMaturity>
</configuration>
</plugin>
Available Parameters
You can pass arbitrary additional configuration properties to the underlying open source OpenAPI generator via <additionalProperties>
.
The complete list of available parameters with their description is as follows:
Parameter | Default | Required | Description |
---|---|---|---|
<inputSpec> | - | Yes | Location of the OpenAPI specification file |
<outputDirectory> | - | Yes | Output directory for generated sources |
<apiPackage> | - | Yes | Package name for the generated API classes |
<modelPackage> | - | Yes | Package name for the generated Model classes |
<deleteOutputDirectory> | False | No | Determines whether to delete the output directory before running the generator |
<apiMaturity> | released | No | Defines the maturity of the OpenAPI for which Java classes are generated. Possible values are released and beta . Please note if you define it as beta then @Beta annotations are added to the generated classes which indicate that they are in an experimental state |
<compileScope> | NONE | No | Adds the generated sources to the compilation or test phase. Respective values are COMPILE and TEST_COMPILE |
<copyrightHeader> | null | No | Copyright header to be added at the top of generated files |
<verbose> | False | No | Run the generator with verbose output |
<sapCopyrightHeader> | False | No | Add the SAP copyright header (overrides copyrightHeader ) |
<enableOneOfAnyOfGeneration> | False | No | Defines whether to enable processing of anyOf/oneOf keywords during client generation |
<additionalProperties> | [] | No | Defines a map of key-value pairs that will be passed to the Java generator. E.g. <param>value</param> |
Customize Java Class and Method Names with OpenAPI Vendor Extensions
The OpenAPI generator uses the tag
field to determine the Java service class name and the operationId
to determine the Java method for each service operation.
You can influence the service class name and the method names by adding extension fields to the OpenAPI specification.
Thereby you can leave the tag
and operationId
fields untouched if they need to stay stable.
Java Class Name
The Java class name can be influenced with the extension field x-sap-cloud-sdk-api-name
.
The OpenAPI generator takes the value of x-sap-cloud-sdk-api-name
and adds an Api
suffix (if not already present) to determine the class name.
In the following example the custom value CustomOperations
takes precedence over the tag
value Operations
/operation/path:
get:
summary: My operation
operationId: myOperation
x-sap-cloud-sdk-api-name: CustomOperations
tags:
- Operations
The Java class for this service operation will be named CustomOperationsApi
.
You can use the extension field x-sap-cloud-sdk-api-name
on an operation, on a path or on the root level of the OpenAPI specification.
All subordinated objects inherit its value automatically.
For instance, if you assign the field x-sap-cloud-sdk-api-name
to one path, all operations under that path inherit this field automatically.
Java Method Name
The Java method name can be overwritten with the extension field x-sap-cloud-sdk-operation-name
.
In the following example the custom value performMyOperation
takes precedence over the operationId
value myOperation
.
/operation/path:
get:
summary: My Operation
x-sap-cloud-sdk-operation-name: performMyOperation
operationId: myOperation
The Java method for this service operation will be named peformMyOperation
.
The order of fields in the OpenAPI specification does not matter.
Only the right nesting is necessary, that is, the two fields x-sap-cloud-sdk-operation-name
and x-sap-cloud-sdk-api-name
should be assigned to an operation (get, post, etc).
Support for anyOf/oneOf
keywords
From version 5.4
of the SAP Cloud SDK, the OpenAPI generator can be configured to generate clients for specification with anyOf/oneOf
keywords by using the <enableOneOfAnyOfGeneration>
parameter in the OpenAPI maven generator plugin.
But, the generated client though may not be feature complete and work as expected for all cases involving anyOf/oneOf
although supporting some use cases.
By default, the client generation for input specification with anyOf/oneOf
keywords in the following locations fails:
- Keywords existing under the
paths
part, under any operation while defining either therequestbody
orresponses
Keywords under paths
paths:
/APIPath:
put:
summary: ...
requestBody:
description: ...
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/SomeSchema'
- $ref: '#/components/schemas/SomeOtherSchema'
responses:
'200':
description: ...
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/SomeSchema'
- type: array
items:
$ref: '#/components/schemas/SomeOtherSchema'
- Keywords existing under any of the
schemas
either under any property inproperties
or underadditionalProperties
Keywords under properties of schema
components:
schemas:
SampleSchema:
title: SampleSchema
description: ...
type: object
properties:
embeddedData:
$ref: '#/components/schemas/EmbeddedData'
questions:
type: array
title: questions
items:
title: Question
oneOf:
- $ref: '#/components/schemas/mc'
- $ref: '#/components/schemas/te'
- $ref: '#/components/schemas/db'
discriminator:
propertyName: type
AnotherSampleSchema:
description: ...
type: ...
anotherProperty:
description: ...
oneOf:
- type: string
enum:
- ENDOFBLOCK
- ENDOFSURVEY
- $ref: '#/components/schemas/SurveyID'
additionalProperties:
oneOf:
- $ref: '#/components/schemas/SomeSchema'
- $ref: '#/components/schemas/SomeOtherSchema'
To make the generation to succeed, please use <enableOneOfAnyOfGeneration>
parameter in the OpenAPI maven generator plugin and re-generate the client again.