Chapter 54. Extending JAX-RS Endpoints with Swagger Support

Abstract

The CXF Swagger2Feature (org.apache.cxf.jaxrs.swagger.Swagger2Feature) allows you to generate Swagger 2.0 documents by extending published JAX-RS service endpoints with a simple configuration.

The Swagger2Feature is supported in both Spring Boot and Karaf implementations.

54.1. Swagger2Feature options

You can use the following options in Swagger2Feature.

Table 54.1. Swagger2Feature operations

NameDescriptionDefault

basePath

The context root path+ (see also the usePathBasedConfig option)

null

contact

Your contact information+

"users@cxf.apache.org"

description

A description+

"The Application"

filterClass

A security filter+

null

host

The host and port information+

null

ignoreRoutes

Excludes specific paths when scanning all resources (see the scanAllResources option)++

null

license

The license+

"Apache 2.0 License"

licenceUrl

The license URL+

http://www.apache.org/licenses/LICENSE-2.0.html

prettyPrint

When generating swagger.json, specifies to pretty-print the JSON document+

false

resourcePackage

A list of comma separated package names where resources must be scanned+

A list of service classes configured at the endpoint

runAsFilter

Runs the feature as a filter

false

scan

Generates the swagger documentation+

true

scanAllResources

Scans all resources including non-annotated JAX-RS resources (see also the ignoreRoutes option)++

false

schemes

The protocol schemes+

null

swaggerUiConfig

Swagger UI configuration

null

termsOfServiceUrl

The terms of service URL+

null

title

The title+

"Sample REST Application"

usePathBasedConfig

Prevents Swagger from caching the value of the basePath option.

false

version

The version+

"1.0.0"

+ The option is defined in Swagger’s BeanConfig

++ The option is defined in Swagger’s ReaderConfig

=== Karaf Implementations

This section describes how to use the Swagger2Feature in which REST services are defined inside JAR files and deployed to a Fuse on Karaf container.

==== 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 Swagger and annotate the JAX-RS endpoints.

==== Enabling Swagger

Enabling Swagger involves:

  • Modifying the XML file that defines the CXF service by adding the CXF class (org.apache.cxf.jaxrs.swagger.Swagger2Feature) to the <jaxrs:server> definition.

    For an example, see Example 55.4 Example XML file.

  • In the REST resource class:

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.swagger.Swagger2Feature">
          <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;
    }

.
.
.

}

=== Spring Boot Implementations

This section describes how to use the Swagger2Feature in Spring Boot.

==== 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.

==== Enabling Swagger

Enabling Swagger involves:

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.2. Accessing Swagger Documents

When Swagger is enabled by Swagger2Feature, the Swagger documents are available at the location URL constructed of the service endpoint location followed by /swagger.json or /swagger.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 Swagger documents are available at http://host:port/context/services/swagger.json and http://host:port/context/services/swagger.yaml.

If Swagger2Feature is active, the CXF Services page links to Swagger documents.

In the above example, you would go to http://host:port/context/services/services and then follow a Swagger link which returns a Swagger JSON document.

If CORS support is needed to access the definition from a Swagger UI on another host, you can add the CrossOriginResourceSharingFilter from cxf-rt-rs-security-cors.

54.3. Accessing Swagger through a reverse proxy

If you want to access a Swagger JSON document or a Swagger 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 your etc directory, you can create one.

  • If you specify a value for the Swagger2Feature basePath option and you want to prevent Swagger from caching the basePath value, set the Swagger2Feature usePathBasedConfig option to TRUE:

    <bean class="org.apache.cxf.jaxrs.swagger.Swagger2Feature">
        <property name="usePathBasedConfig" value="TRUE" />
    </bean>