A Maven plug-in for generating Swagger documentation for JAX-RS REST services
Java
Permalink
Failed to load latest commit information.
.settings add support for Java 8 Dec 2, 2015
src move root package name May 17, 2016
.classpath
.gitignore 1.3.0 release May 19, 2016
.project initial cut at generating Swagger documentation for a JAX-RS service Mar 14, 2014
LICENSE switch licensing to Apache 2 Feb 12, 2016
pom.xml set version to snapshot May 19, 2016
readme.textile

readme.textile

Overview

This project provides a Maven plug-in that can generate Swagger documentation for JAX-RS web services. The goal is to produce documentation for REST web services at build-time rather than run-time so that applicaiton runtime dependencies are kept to a minimum.

What it does

  1. Generates Swagger documentation as .json files at build-time
  2. Handles a specific style of JAX-RS service authoring; for example it expects annotations to be fairly complete

Motivation

This project was created because documentation for web services is important and Swagger is a great way to do it. To date Swagger documentation is either written completely by hand or is generated using Swagger annotations at runtime. Authoring Swagger docs from scratch means that the documentation is separate from the source code and it’s easy for the documentation to become inaccurate. Runtime generation of Swagger documentation adds a huge number of dependencies to your project’s runtime (about 30 the last time I counted). Dependencies add complexity and maintenance overhead, and also add overhead to maintaining an IP-clean project.

The goal of this project is to enable annotation-based documentation of JAX-RS web services without adding numerous project runtime dependencies.

Project Requirements

In order to generate Swagger documentation for your project the following requirements must be met:

  • Your project has at least one JAX-RS for REST-based services
  • Your JAX-RS service is annotated with Swagger API annotations
  • Your project has a Maven build

This has been tested with Jersey JAX-RS services, but does not depend on Jersey directly so should work with JAX-RS services targeting other frameworks as well.

Maven Project Setup

Your project’s Maven pom should look something like this:

Add a Swagger annotations dependency so that Swagger annotations can be used in your project:


    <dependency>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-annotations</artifactId>
      <version>1.5.3</version>
    </dependency>

Add this plug-in to your pom.xml’s build:

<build>
		<finalName>web</finalName>
		<plugins>
			<plugin>
				<groupId>greensopinion.swagger</groupId>
				<artifactId>jaxrs-gen</artifactId>
				<version>1.3.0</version>
				<configuration>
					<apiVersion>1.0</apiVersion>
					<apiBasePath>/api/latest</apiBasePath>
					<packageNames>
						<param>my.project.package.name</param> <!-- the package name of your project's JAX-RS web services. -->
					</packageNames>
					<outputFolder>${project.build.directory}/web/api/docs</outputFolder>
				</configuration>
				<executions>
					<execution>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

Building Documentation

Documentation is created as part of the compile step as follows:

mvn compile

Your Maven output should look something like this:

[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building web Maven Webapp 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ web ---
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ web ---
[INFO] Compiling 13 source files to /myproject/web/target/classes
[INFO] 
[INFO] --- jaxrs-gen:1.0-SNAPSHOT:generate (default) @ web ---
[INFO] API class: myproject.web.MyService
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.796s
[INFO] Finished at: Fri Mar 14 07:35:36 PDT 2014
[INFO] Final Memory: 19M/310M
[INFO] ------------------------------------------------------------------------

Generated file layout

Generated documentation is created in files in a nested folder structure that mimics the layout of your services. For example, if you have two REST services as follows:

@Path("/pet")
public class PetService { ... }
@Path("/owner")
public class OwnerService { ... }

The generated documentation would be created as follows:

<outputFolder>
  +- index.json     <-- the Swagger resource listing
  +- pet
  |  +- index.json  <-- the pet Swagger API declaration 
  +- owner
     +- index.json  <-- the owner Swagger API declaration

This layout combined with the Web Application configuration below will enable Swagger UI to interact with the services.

Web Application

I recommend having your web application serve up the Swagger documentation; that way your documentation is always up to date and available for those using its REST services.

The following addition to your web.xml will cause the generated documentation to be served as expected:

<welcome-file-list>
    <!-- other welcome files go here --> 
    <welcome-file>index.json</welcome-file>
</welcome-file-list>

Example JAX-RS Service

This Maven plug-in expects your JAX-RS service to look something like this:


@Path("/pets")
@Api(value = "/pets", description = "Operations about pets")
@Produces(MediaType.APPLICATION_JSON)
public class PetService {

	@GET
	@ApiOperation(value = "List all pets", notes = "List all pets. Results are paginated.", response = PetListing.class)
	public PetListing list(@QueryParam("start") @DefaultValue("0") int start,
			@QueryParam("count") @DefaultValue("50") int count) {
		return null;
	}

	@GET
	@Path("/{id}")
	@ApiOperation(value = "Retrieve pet by id", notes = "Retrieves a pet by it's id.", response = Pet.class)
	@ApiResponses(value = { @ApiResponse(code = 404, message = "Pet not found", response = ServerError.class) })
	public Pet retrievePet(@PathParam("id") long id) {
		return null;
	}

	@DELETE
	@Path("/{id}")
	@ApiOperation(value = "Delete pet by id", notes = "Deletes a pet by it's id.")
	@ApiResponses(value = { @ApiResponse(code = 404, message = "Pet not found", response = ServerError.class) })
	public void deletePet(@PathParam("id") long id) {
	}

	@PUT
	@ApiOperation(value = "Creates a new pet", notes = "Creates a new pet with the given name.", response = PetHandle.class)
	public PetHandle createPet(PetValues petValues) {
		return null;
	}

	@POST
	@Path("/{id}")
	@Produces(MediaType.APPLICATION_JSON)
	@ApiOperation(value = "Updates a pet", notes = "Updates an existing pet with the provided details.")
	@ApiResponses(value = { @ApiResponse(code = 404, message = "Pet not found", response = ServerError.class) })
	public void updatePet(Pet pet, @PathParam("id") long id) {
	}
}

The following Swagger description was generated from the above API:

api-docs.json


{
  "apiVersion": "1.0",
  "swaggerVersion": "1.2",
  "apis": [
    {
      "path": "/page",
      "description": "Operations about wiki pages"
    }
  ]
}

pet.json


{
  "apiVersion": "1.0.1",
  "swaggerVersion": "1.2",
  "basePath": "/api/latest",
  "resourcePath": "/pets/{petId}",
  "description": "Operations about pets",
  "apis": [
    {
      "path": "/pet",
      "description": "Operations about pets",
      "operations": [
        {
          "method": "GET",
          "summary": "List all pets",
          "notes": "List all pets. Results are paginated.",
          "type": "PetListing",
          "nickname": "list",
          "produces": [
            "application/json"
          ],
          "parameters": [
            {
              "name": "start",
              "defaultValue": "0",
              "required": false,
              "allowMultiple": false,
              "type": "integer",
              "format": "int32",
              "paramType": "query"
            },
            {
              "name": "count",
              "defaultValue": "50",
              "required": false,
              "allowMultiple": false,
              "type": "integer",
              "format": "int32",
              "paramType": "query"
            }
          ]
        },
        {
          "method": "PUT",
          "summary": "Creates a new pet",
          "notes": "Creates a new pet with the given name.",
          "type": "PetHandle",
          "nickname": "createPet",
          "produces": [
            "application/json"
          ],
          "parameters": [
            {
              "name": "body",
              "required": true,
              "allowMultiple": false,
              "type": "PetValues",
              "paramType": "body"
            }
          ]
        }
      ]
    },
    {
      "path": "/pet/{id}",
      "description": "Operations about pets",
      "operations": [
        {
          "method": "DELETE",
          "summary": "Delete pet by id",
          "notes": "Deletes a pet by it's id.",
          "type": "void",
          "nickname": "deletePet",
          "produces": [
            "application/json"
          ],
          "parameters": [
            {
              "name": "id",
              "required": true,
              "allowMultiple": false,
              "type": "integer",
              "format": "int64",
              "paramType": "path"
            }
          ],
          "responseMessages": [
            {
              "code": 404,
              "message": "Pet not found",
              "responseModel": "ServerError"
            }
          ]
        },
        {
          "method": "GET",
          "summary": "Retrieve pet by id",
          "notes": "Retrieves a pet by it's id.",
          "type": "Pet",
          "nickname": "retrievePet",
          "produces": [
            "application/json"
          ],
          "parameters": [
            {
              "name": "id",
              "required": true,
              "allowMultiple": false,
              "type": "integer",
              "format": "int64",
              "paramType": "path"
            }
          ],
          "responseMessages": [
            {
              "code": 404,
              "message": "Pet not found",
              "responseModel": "ServerError"
            }
          ]
        },
        {
          "method": "POST",
          "summary": "Updates a pet",
          "notes": "Updates an existing pet with the provided details.",
          "type": "void",
          "nickname": "updatePet",
          "produces": [
            "application/json"
          ],
          "parameters": [
            {
              "name": "body",
              "required": true,
              "allowMultiple": false,
              "type": "Pet",
              "paramType": "body"
            },
            {
              "name": "id",
              "required": true,
              "allowMultiple": false,
              "type": "integer",
              "format": "int64",
              "paramType": "path"
            }
          ],
          "responseMessages": [
            {
              "code": 404,
              "message": "Pet not found",
              "responseModel": "ServerError"
            }
          ]
        }
      ]
    }
  ],
  "models": {
    "Pet": {
      "id": "Pet",
      "required": [
        "id",
        "name",
        "kind"
      ],
      "properties": {
        "id": {
          "type": "integer",
          "format": "int64"
        },
        "name": {
          "type": "string",
          "description": "The name of the pet"
        },
        "kind": {
          "type": "PetKind",
          "description": "The kind of pet",
          "enum": [
            "DOG",
            "CAT",
            "REPTILE",
            "FISH",
            "HORSE",
            "OTHER"
          ]
        },
        "notes": {
          "type": "string"
        }
      }
    },
    "PetHandle": {
      "id": "PetHandle",
      "required": [
        "id"
      ],
      "properties": {
        "id": {
          "type": "integer",
          "format": "int64"
        },
        "name": {
          "type": "string",
          "description": "The name of the pet"
        }
      }
    },
    "PetListing": {
      "id": "PetListing",
      "description": "A listing of pets",
      "required": [],
      "properties": {
        "start": {
          "type": "integer",
          "format": "int32",
          "description": "The 0-based start index offset."
        },
        "count": {
          "type": "integer",
          "format": "int32",
          "description": "The number of pets requested.  May differ from the actual number of pets in the listing."
        },
        "total": {
          "type": "integer",
          "format": "int32",
          "description": "The total number of pets in the system that correspond to the listing."
        },
        "pets": {
          "type": "List",
          "items": {
            "$ref": "PetHandle"
          },
          "description": "The list of pet handles."
        }
      }
    },
    "PetValues": {
      "id": "PetValues",
      "required": [
        "name",
        "kind"
      ],
      "properties": {
        "name": {
          "type": "string",
          "description": "The name of the pet"
        },
        "kind": {
          "type": "PetKind",
          "description": "The kind of pet",
          "enum": [
            "DOG",
            "CAT",
            "REPTILE",
            "FISH",
            "HORSE",
            "OTHER"
          ]
        },
        "notes": {
          "type": "string"
        }
      }
    },
    "ServerError": {
      "id": "ServerError",
      "description": "A representation of an error, providing an error code and message.",
      "required": [
        "code"
      ],
      "properties": {
        "code": {
          "type": "string",
          "description": "The error code, which can be used to identify the type of error."
        },
        "message": {
          "type": "string",
          "description": "The message describing the error."
        },
        "detail": {
          "type": "string",
          "description": "Detail of the error which can be used for diagnostic purposes."
        }
      }
    }
  }
}

License

Licensed under the Apache License, Version 2.0 (the “License”);
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

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

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.