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.
Name | Description | Default |
---|---|---|
| The OpenAPI configuration location | null |
| The contact email+ | null |
| The contact name+ | null |
| The contact link+ | null |
| The customizer class instance | null |
| The description+ | null |
| A security filter++ | null |
|
Excludes specific paths when scanning all resources (see | null |
| The license+ | null |
| The license URL+ | null |
| When generating openapi.json, pretty-print the JSON document++ | true |
| The properties file location |
|
| Read all operations also with no @Operation++ | true |
| A list of resource classes which must be scanned++ | null |
| A list of package names where resources must be scanned++ | null |
| Runs the feature as a filter | false |
| Scan all JAX-RS resources automatically | true |
| Scan known OpenAPI configuration location (classpath or filesystem), which are: openapi-configuration.yaml openapi-configuration.json openapi.yaml openapi.json | true |
| The name of the JAX-RS API scanner class, used to scope the application, resource packages, resource classes and classpath scanning, please refer to Resource Scanning section | null |
| A list of security definitions+ | null |
| Turns on/off SwaggerUI support | null (== true) |
| Swagger UI configuration | null |
| The Maven artifacts to pinpoint SwaggerUI | null |
| The version of SwaggerUI | null |
| The terms of service URL+ | null |
| The title+ | null |
| If set, the unique Context Id is going to be generated for each OpenApiContext instance (please see Using Multiple Server Endpoints). Also, you may want to set scan property to false. | false |
| The version+ | null |
+ The option is defined in the OpenAPI class
++ The option is defined in the SwaggerConfiguration class
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.swagger.annotations.*
where * =
Api
,ApiOperation
,ApiParam
,ApiResponse
,ApiResponses
, and so on.For details, go to
https://github.com/swagger-api/swagger-core/wiki/Annotations-1.5.X
.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.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiResponse; import io.swagger.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-headers
init 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.cfg
configuration file:cxf.servlet.init.use-x-forwarded-headers=true
Note: If you do not already have an
org.apache.cxf.osgi.cfg
file in youretc
directory, you can create one.
If you specify a value for the OpenApiFeature
basePath
option and you want to prevent OpenAPI from caching thebasePath
value, set the OpenApiFeatureusePathBasedConfig
option to TRUE:<bean class="org.apache.cxf.jaxrs.openapi.OpenApiFeature"> <property name="usePathBasedConfig" value="TRUE" /> </bean>