Tuesday, October 27, 2015

Using SpringFox for API document generation.

While working on Spring MVC based RESTful services that expose APIs for other developers to use, I needed a library that could read the API specifications in the Controller classes, generate API documentation (so that it is always up-to-date with the changes to the API spec) and better if it can provide a way to invoke these APIs. Springfox's implementation of Swagger appeared to fit these requirements perfectly.

This post is a log of changes I made to my Spring MVC RESTful service to integrate with Springfox's Swagger implementation.
  1. Included the maven dependencies for swagger-springfox to expose the API details and swagger-springfox-ui to render the documentation and provide a "Try it out" interface to invoke these APIs.
  2.  
        <dependency>
            <groupid>io.springfox</groupid>
            <artifactid>springfox-swagger2</artifactid>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupid>io.springfox</groupid>
            <artifactid>springfox-swagger-ui</artifactid>
            <version>2.2.2</version>
        </dependency>
    
    
  3. Created a Configuration class to initialize SpringFox
  4. import static com.google.common.collect.Lists.newArrayList;
    import static springfox.documentation.schema.AlternateTypeRules.newRule;
    
    import org.joda.time.LocalDate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.context.request.async.DeferredResult;
    
    import com.fasterxml.classmate.TypeResolver;
    
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.builders.ResponseMessageBuilder;
    import springfox.documentation.schema.ModelRef;
    import springfox.documentation.schema.WildcardType;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    @Configuration
    @EnableSwagger2
    public class SwaggerConfig
    {
        @Autowired
        private TypeResolver typeResolver;
    
        @Bean
        public Docket petApi()
        {
            return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                .pathMapping("/")
                .directModelSubstitute(LocalDate.class, String.class)
                .genericModelSubstitutes(ResponseEntity.class)
                .alternateTypeRules(
                    newRule(
                        typeResolver.resolve(
                            DeferredResult.class,
                            typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
                        typeResolver.resolve(WildcardType.class)))
                .useDefaultResponseMessages(false)
                .globalResponseMessage(
                    RequestMethod.GET,
                    newArrayList(
                        new ResponseMessageBuilder()
                            .code(500)
                            .message("500 message")
                            .responseModel(new ModelRef("Error"))
                            .build()))
                .enableUrlTemplating(false);
        }
    
    }
    
    
  5. With the addition of Springfox Configuration above, the test cases started failing due to missing ServletContext bean. Added the @WebAppConfiguration annotation to the parent test class that also has the test spring configuration initialized.
  6. 
    
    import org.junit.Ignore;
    import org.junit.runner.RunWith;
    import org.springframework.test.annotation.DirtiesContext;
    import org.springframework.test.annotation.DirtiesContext.ClassMode;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.context.web.WebAppConfiguration;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = { "classpath:test-application-spring-config.xml" })
    @DirtiesContext(classMode = ClassMode.AFTER_CLASS)
    @WebAppConfiguration
    @Ignore("Ingoring an abstract test class")
    public abstract class AbstractIntegrationTest
    {
    
    }
    
    
    
  7. Added the following resource mappings to serve the swagger related resources from springfox-swagger-ui.jar.
  8. 
    <mvc:resources location="classpath:/META-INF/resources/swagger-ui.html" mapping="swagger-ui.html">    <mvc:resources location="classpath:/META-INF/resources/webjars/" mapping="/webjars/**">
    </mvc:resources></mvc:resources>
    
    
Notes:
  1. Initially, I set the "enableUrlTemplating" attribute to "true" in SwaggerConfiguration. Due to this the URLs in "Try it out" section included placeholders for the request parameters, making this feature use less.

    E.g.
    URL when the attribute is set to true:
            http://localhost:8080/my-api{?param1,param2}?param1=&param2=&

    URL after the attribute value is changed to false:
            http://localhost:8080/my-api?param1=&param2=&
  2. At first, I included the mvc resource mappings in the SwaggerConfiguration class itself, by extending the WebMvcConfigurerAdapter class, but access to swagger-ui.html returned 404 error. So, I removed this from here and added them to the XML configuration and then the swagger-ui.html came up properly.
  3.     @Override
        public void addResourceHandlers(final ResourceHandlerRegistry registry)
        {
            registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
            registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        }
    
    
  4. I tried to render swagger-ui.html under a different URL (e.g. /my-service-api) but, due to hard coded lookup for swagger-ui.html in the springfox.js, even though the page loaded, it couldn't proceed further to make call to the /v2/api-docs to get the JSON and render it.

2 comments :