Swagger and Spark Java integration

Thu, May 12, 2016

I love Spark framework, it’s simple, fast and doesn’t require you to dive into configuration files. You can find further and shiny details on project webpage:

Spark - A micro framework for creating web applications in Java 8 with minimal effort.

I think Spark might be one of the best platforms to build Rest APIs and Swagger is the best tool to document REST APIs. However as Spark developers love using lambda support and Swagger relies on annotations, they don’t really play nice together. However if you can cease to use lambda expressions for the routes, it’s fairly simple to create API description with Swagger.

Let’s see how it plays on a horribly designed blood donation application. The complete code for the project is on https://github.com/srlk/spark-swagger .

First of all, pom.xml file contains dependencies below:

<!-- spark java -->
<dependency>
	<groupId>com.sparkjava</groupId>
	<artifactId>spark-core</artifactId>
	<version>2.3</version>
</dependency>
<!-- reflection -->
<dependency>
	<groupId>org.reflections</groupId>
	<artifactId>reflections</artifactId>
	<version>0.9.10</version>
</dependency>
<!-- swagger -->
<dependency>
	<groupId>io.swagger</groupId>
	<artifactId>swagger-core</artifactId>
	<version>1.5.8</version>
</dependency>
<dependency>
	<groupId>io.swagger</groupId>
	<artifactId>swagger-jaxrs</artifactId>
	<version>1.5.8</version>
</dependency>
<!-- jax rs annotations -->
<dependency>
	<groupId>javax.ws.rs</groupId>
	<artifactId>javax.ws.rs-api</artifactId>
	<version>2.0.1</version>
</dependency>

Then a class in the project should contain API description as annotations, probably the class containing main method or creating a separate interface are the best choices.


@SwaggerDefinition(host = "localhost:4567", //
info = @Info(description = "DonateAPP API", //
version = "V1.0", //
title = "Some random api for testing", //
contact = @Contact(name = "Serol", url = "https://serol.ro") ) , //
schemes = { SwaggerDefinition.Scheme.HTTP, SwaggerDefinition.Scheme.HTTPS }, //
consumes = { "application/json" }, //
produces = { "application/json" }, //
tags = { @Tag(name = "swagger") })
public class App {

	public static void main(String[] args) {
	    ...
	}

}

And each route has to be separate classes to make annotations happily parsesable.. Sorry (req, res) -> magic! :( And example route is below:

@Api
@Path("/user")
@Produces("application/json")
public class CreateUserRoute implements Route {

	@POST
	@ApiOperation(value = "Creates a new user", nickname="CreateUserRoute")
	@ApiImplicitParams({ //
			@ApiImplicitParam(required = true, dataType="string", name="auth", paramType = "header"), //
			@ApiImplicitParam(required = true, dataType = "ro.serol.spark_swagger.route.request.CreateUserRequest", paramType = "body") //
	}) //
	@ApiResponses(value = { //
			@ApiResponse(code = 200, message = "Success", response=User.class), //
			@ApiResponse(code = 400, message = "Invalid input data", response=ApiError.class), //
			@ApiResponse(code = 401, message = "Unauthorized", response=ApiError.class), //
			@ApiResponse(code = 404, message = "User not found", response=ApiError.class) //
	})
	public User handle(@ApiParam(hidden=true) Request request, @ApiParam(hidden=true)Response response) throws Exception {
		return new User();
	}

}

Now with the help of swagger-jaxrs we can parse our API description:

public class SwaggerParser {

	public static String getSwaggerJson(String packageName) throws JsonProcessingException {
		Swagger swagger = getSwagger(packageName);
		String json = swaggerToJson(swagger);
		return json;
	}

	public static Swagger getSwagger(String packageName) {
		Reflections reflections = new Reflections(packageName);
		BeanConfig beanConfig = new BeanConfig();
		beanConfig.setResourcePackage(packageName);
		beanConfig.setScan(true);
		beanConfig.scanAndRead();
		Swagger swagger = beanConfig.getSwagger();

		Reader reader = new Reader(swagger);

		Set<Class<?>> apiClasses = reflections.getTypesAnnotatedWith(Api.class);
		return reader.read(apiClasses);
	}

	public static String swaggerToJson(Swagger swagger) throws JsonProcessingException {
		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.setSerializationInclusion(Include.NON_EMPTY);
		String json = objectMapper.writeValueAsString(swagger);
		return json;
	}

}

Putting that in main method:

@SwaggerDefinition(host = "localhost:4567", //
info = @Info(description = "DonateAPP API", //
version = "V1.0", //
title = "Some random api for testing", //
contact = @Contact(name = "Serol", url = "https://serol.ro") ) , //
schemes = { SwaggerDefinition.Scheme.HTTP, SwaggerDefinition.Scheme.HTTPS }, //
consumes = { "application/json" }, //
produces = { "application/json" }, //
tags = { @Tag(name = "swagger") })
public class App {

	public static final String APP_PACKAGE = "ro.serol.spark_swagger";

	public static void main(String[] args) {
        ...
		try {
			// Build swagger json description
			final String swaggerJson = SwaggerParser.getSwaggerJson(APP_PACKAGE);
			get("/swagger", (req, res) -> {
				return swaggerJson;
			});

		} catch (Exception e) {
			System.err.println(e);
			e.printStackTrace();
		}
	}

}

Now browsing to http://localhost:4567/swagger returns us our API documentation as JSON. Generated JSON can be downloaded here.

And you can merrily use that JSON in Swagger Editor like this!



 
Next: »