Chapter 54. Extending JAX-RS Endpoints with OpenAPI Support
Abstract
The CXF OpenApiFeature (org.apache.cxf.jaxrs.openapi.OpenApiFeature) allows you to generate OpenAPI documents by extending published JAX-RS service endpoints with a simple configuration.
The OpenApiFeature is supported in both Spring Boot and Karaf implementations.
54.1. OpenApiFeature options
You can use the following options in OpenApiFeature.
Table 54.1. OpenApiFeature operations
| Name | Description | Default |
|---|---|---|
|
|
The context root path+ (see also the | null |
|
| Your contact information+ | |
|
| A description+ | "The Application" |
|
| A security filter+ | null |
|
| The host and port information+ | null |
|
|
Excludes specific paths when scanning all resources (see the | null |
|
| The license+ | "Apache 2.0 License" |
|
| The license URL+ | |
|
|
When generating | false |
|
| A list of comma separated package names where resources must be scanned+ | A list of service classes configured at the endpoint |
|
| Runs the feature as a filter | false |
|
| Generates the OpenAPI documentation+ | true |
|
|
Scans all resources including non-annotated JAX-RS resources (see also the | false |
|
| The protocol schemes+ | null |
|
| OpenAPI UI configuration | null |
|
| The terms of service URL+ | null |
|
| The title+ | "Sample REST Application" |
|
|
Prevents OpenAPI from caching the value of the | false |
|
| The version+ | "1.0.0" |
+ The option is defined in OpenAPIs’s BeanConfig
++ The option is defined in OpenAPI’s ReaderConfig
54.2. Karaf Implementations
This section describes how to use the OpenApiFeature in which REST services are defined inside JAR files and deployed to a Fuse on Karaf container.
54.2.1. Quickstart example
You can download Red Hat Fuse quickstarts from the Fuse Software Downloads page.
The Quickstart zip file contains a /cxf/rest/ directory for a quickstart that demonstrates how to create a RESTful (JAX-RS) web service using CXF and how to enable OpenAPI and annotate the JAX-RS endpoints.
54.2.2. Enabling OpenAPI
Enabling OpenAPI involves:
Modifying the XML file that defines the CXF service by adding the CXF class (
org.apache.cxf.jaxrs.openapi.OpenApiFeature) to the<jaxrs:server>definition.For an example, see Example 55.4 Example XML file.
In the REST resource class:
Importing the OpenAPI annotations for each annotation required by the service:
import io.openapi.annotations.*
where * =
Api,ApiOperation,ApiParam,ApiResponse,ApiResponses, and so on.For details, go to
https://github.com/openapi-api/openapi-core/wiki/Annotations.For an example, see Example 55.5 Example Resource class.
-
Adding OpenAPI annotations to the JAX-RS annotated endpoints (
@PATH,@PUT,@POST,@GET,@Produces,@Consumes,@DELETE,@PathParam, and so on).
For an example, see Example 55.5 Example Resource class.
Example 55.4 Example XML file
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"
xmlns:cxf="http://cxf.apache.org/blueprint/core"
xsi:schemaLocation="
http://www.osgi.org/xmlns/blueprint/v1.0.0
https://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://cxf.apache.org/blueprint/jaxrs
http://cxf.apache.org/schemas/blueprint/jaxrs.xsd
http://cxf.apache.org/blueprint/core
http://cxf.apache.org/schemas/blueprint/core.xsd">
<jaxrs:server id="customerService" address="/crm">
<jaxrs:serviceBeans>
<ref component-id="customerSvc"/>
</jaxrs:serviceBeans>
<jaxrs:providers>
<bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/>
</jaxrs:providers>
<jaxrs:features>
<bean class="org.apache.cxf.jaxrs.openapi.OpenApiFeature">
<property name="title" value="Fuse:CXF:Quickstarts - Customer Service" />
<property name="description" value="Sample REST-based Customer Service" />
<property name="version" value="${project.version}" />
</bean>
</jaxrs:features>
</jaxrs:server>
<cxf:bus>
<cxf:features>
<cxf:logging />
</cxf:features>
<cxf:properties>
<entry key="skip.default.json.provider.registration" value="true" />
</cxf:properties>
</cxf:bus>
<bean id="customerSvc" class="org.jboss.fuse.quickstarts.cxf.rest.CustomerService"/>
</blueprint>
Example 55.5 Example Resource class
.
.
.
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import io.openapi.annotations.Api;
import io.openapi.annotations.ApiOperation;
import io.openapi.annotations.ApiParam;
import io.openapi.annotations.ApiResponse;
import io.openapi.annotations.ApiResponses;
.
.
.
@Path("/customerservice/")
@Api(value = "/customerservice", description = "Operations about customerservice")
public class CustomerService {
private static final Logger LOG =
LoggerFactory.getLogger(CustomerService.class);
private MessageContext jaxrsContext;
private long currentId = 123;
private Map<Long, Customer> customers = new HashMap<>();
private Map<Long, Order> orders = new HashMap<>();
public CustomerService() {
init();
}
@GET
@Path("/customers/{id}/")
@Produces("application/xml")
@ApiOperation(value = "Find Customer by ID", notes = "More notes about this
method", response = Customer.class)
@ApiResponses(value = {
@ApiResponse(code = 500, message = "Invalid ID supplied"),
@ApiResponse(code = 204, message = "Customer not found")
})
public Customer getCustomer(@ApiParam(value = "ID of Customer to fetch",
required = true) @PathParam("id") String id) {
LOG.info("Invoking getCustomer, Customer id is: {}", id);
long idNumber = Long.parseLong(id);
return customers.get(idNumber);
}
@PUT
@Path("/customers/")
@Consumes({ "application/xml", "application/json" })
@ApiOperation(value = "Update an existing Customer")
@ApiResponses(value = {
@ApiResponse(code = 500, message = "Invalid ID supplied"),
@ApiResponse(code = 204, message = "Customer not found")
})
public Response updateCustomer(@ApiParam(value = "Customer object that needs
to be updated", required = true) Customer customer) {
LOG.info("Invoking updateCustomer, Customer name is: {}", customer.getName());
Customer c = customers.get(customer.getId());
Response r;
if (c != null) {
customers.put(customer.getId(), customer);
r = Response.ok().build();
} else {
r = Response.notModified().build();
}
return r;
}
@POST
@Path("/customers/")
@Consumes({ "application/xml", "application/json" })
@ApiOperation(value = "Add a new Customer")
@ApiResponses(value = { @ApiResponse(code = 500, message = "Invalid ID
supplied"), })
public Response addCustomer(@ApiParam(value = "Customer object that needs to
be updated", required = true) Customer customer) {
LOG.info("Invoking addCustomer, Customer name is: {}", customer.getName());
customer.setId(++currentId);
customers.put(customer.getId(), customer);
if (jaxrsContext.getHttpHeaders().getMediaType().getSubtype().equals("json"))
{
return Response.ok().type("application/json").entity(customer).build();
} else {
return Response.ok().type("application/xml").entity(customer).build();
}
}
@DELETE
@Path("/customers/{id}/")
@ApiOperation(value = "Delete Customer")
@ApiResponses(value = {
@ApiResponse(code = 500, message = "Invalid ID supplied"),
@ApiResponse(code = 204, message = "Customer not found")
})
public Response deleteCustomer(@ApiParam(value = "ID of Customer to delete",
required = true) @PathParam("id") String id) {
LOG.info("Invoking deleteCustomer, Customer id is: {}", id);
long idNumber = Long.parseLong(id);
Customer c = customers.get(idNumber);
Response r;
if (c != null) {
r = Response.ok().build();
customers.remove(idNumber);
} else {
r = Response.notModified().build();
}
return r;
}
.
.
.
}
54.3. Spring Boot Implementations
This section describes how to use the Swagger2Feature in Spring Boot.
Note that for OpenAPI 3 implementations, use the OpenApiFeature(org.apache.cxf.jaxrs.openapi.OpenApiFeature).
54.3.1. Quickstart example
The Quickstart example (https://github.com/fabric8-quickstarts/spring-boot-cxf-jaxrs) demonstrates how you can use Apache CXF with Spring Boot. The Quickstart uses Spring Boot to configure an application that includes a CXF JAX-RS endpoint with Swagger enabled.
54.3.2. Enabling Swagger
Enabling Swagger involves:
In the REST application:
Importing Swagger2Feature:
import org.apache.cxf.jaxrs.swagger.Swagger2Feature;
Adding Swagger2Feature to a CXF endpoint:
endpoint.setFeatures(Arrays.asList(new Swagger2Feature()));
For an example, see Example 55.1 Example REST application.
In the Java implementation file, importing the Swagger API annotations for each annotation required by the service:
import io.swagger.annotations.*
where * =
Api,ApiOperation,ApiParam,ApiResponse,ApiResponses, and so on.For details, see
https://github.com/swagger-api/swagger-core/wiki/Annotations.For an example, see Example 55.2 Example Java implementation file.
In the Java file, adding Swagger annotations to the JAX-RS annotated endpoints (
@PATH,@PUT,@POST,@GET,@Produces,@Consumes,@DELETE,@PathParam, and so on).For an example, see Example 55.3 Example Java file.
Example 55.1 Example REST application
package io.fabric8.quickstarts.cxf.jaxrs;
import java.util.Arrays;
import org.apache.cxf.Bus;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.swagger.Swagger2Feature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SampleRestApplication {
@Autowired
private Bus bus;
public static void main(String[] args) {
SpringApplication.run(SampleRestApplication.class, args);
}
@Bean
public Server rsServer() {
// setup CXF-RS
JAXRSServerFactoryBean endpoint = new JAXRSServerFactoryBean();
endpoint.setBus(bus);
endpoint.setServiceBeans(Arrays.<Object>asList(new HelloServiceImpl()));
endpoint.setAddress("/");
endpoint.setFeatures(Arrays.asList(new Swagger2Feature()));
return endpoint.create();
}
}
Example 55.2 Example Java implementation file
import io.swagger.annotations.Api;
@Api("/sayHello")
public class HelloServiceImpl implements HelloService {
public String welcome() {
return "Welcome to the CXF RS Spring Boot application, append /{name} to call the hello service";
}
public String sayHello(String a) {
return "Hello " + a + ", Welcome to CXF RS Spring Boot World!!!";
}
}
Example 55.3 Example Java file
package io.fabric8.quickstarts.cxf.jaxrs;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.springframework.stereotype.Service;
@Path("/sayHello")
@Service
public interface HelloService {
@GET
@Path("")
@Produces(MediaType.TEXT_PLAIN)
String welcome();
@GET
@Path("/{a}")
@Produces(MediaType.TEXT_PLAIN)
String sayHello(@PathParam("a") String a);
}
54.4. Accessing OpenAPI Documents
When OpenAPI is enabled by OpenApiFeature, the OpenAPI documents are available at the location URL constructed of the service endpoint location followed by /openapi.json or /openapi.yaml.
For example, for a JAX-RS endpoint that is published at http://host:port/context/services/ where context is a web application context and /services is a servlet URL, its OpenAPI documents are available at http://host:port/context/services/openapi.json and http://host:port/context/services/openapi.yaml.
If OpenApiFeature is active, the CXF Services page links to OpenAPI documents.
In the above example, you would go to http://host:port/context/services/services and then follow a link which returns an OpenAPI JSON document.
If CORS support is needed to access the definition from an OpenAPI UI on another host, you can add the CrossOriginResourceSharingFilter from cxf-rt-rs-security-cors.
54.5. Accessing OpenAPI through a reverse proxy
If you want to access an OpenAPI JSON document or an OpenAPI UI through a reverse proxy, set the following options:
Set the
CXFServlet use-x-forwarded-headersinit parameter to true.In Spring Boot, prefix the parameter name with
cxf.servlet.init:cxf.servlet.init.use-x-forwarded-headers=true
In Karaf, add the following line to the
installDir/etc/org.apache.cxf.osgi.cfgconfiguration file:cxf.servlet.init.use-x-forwarded-headers=true
Note: If you do not already have an
org.apache.cxf.osgi.cfgfile in youretcdirectory, you can create one.
If you specify a value for the OpenApiFeature
basePathoption and you want to prevent OpenAPI from caching thebasePathvalue, set the OpenApiFeatureusePathBasedConfigoption to TRUE:<bean class="org.apache.cxf.jaxrs.openapi.OpenApiFeature"> <property name="usePathBasedConfig" value="TRUE" /> </bean>