How to apply Springfox Swagger Global Parameters on Rest API CRUD

{getToc} $title={Table of Contents}

How to apply Springfox Swagger Global Parameters on Rest API CRUD


1. Introduction

In this tutorial, we'll look at Swagger 2 for a Spring REST web service, using the Springfox or springfox repo on github implementation of the Swagger 2 specification. If you are not familiar with Swagger, visit its web page to learn more.

I'm goint to apply it on my previouse existed Rest APIs CRUD with JDBI project. And I'm about to add a custom header in all APIs and use default response messages globally without writing in each API method too.


Documentation is a very important part of any restful API, Swagger had made it easy for developers to get a neat documentation for their API endpoints that is readable for both humans and machines only with a few steps. Swagger lists all API endpoints with detailed information about them like description, parameters and output schema in JSON format while Swagger-UI provides a styled interface with the ability to test the endpoints.

2. Dependency

Add the springfox dependency. The latest version can be downloaded from Maven Central.

<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-boot-starter</artifactId>
	<version>3.0.0</version>
</dependency>


3. Configuration

Swagger Configuration with Global Request Parameter and Responses

Swagger configuration is done using Docket Bean, below is a simple configuration to integrate Swagger with Spring Boot, you can add more customization to your documentation in the Docket Bean, also you may create more than one Docket Bean.

package com.yuthads.springdatajdbi3crud.configuration;

import static com.google.common.collect.Lists.newArrayList;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;

import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.RequestParameterBuilder;
import springfox.documentation.builders.ResponseBuilder;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.ParameterType;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import springfox.documentation.swagger.web.OperationsSorter;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;

@Configuration
public class SwaggerConfig {

	private ApiInfo apiInfo() {
		return new ApiInfo("SpringFox Demo APIs",
        	"Demo APIs with spring boot rest api by usgin jdbi3","1.0","Terms of service",
        	new Contact("YUTHADS","https://yuthads.blogspot.com","yuthads@mail.com"),
        	"License of API", "API license URL",Collections.emptyList());
	}

	@Bean
	Docket api() {
    	return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
        	.securityContexts(Arrays.asList(securityContext()))
        	.securitySchemes(Arrays.asList(apiKey())).select()
        	.apis(RequestHandlerSelectors
            	.basePackage("com.yuthads.springdatajdbi3crud.controller"))
        	.paths(PathSelectors.ant("/api/product/**"))
            	.build().useDefaultResponseMessages(false)
        	.globalResponses(HttpMethod.GET,newArrayList(new ResponseBuilder()
        	.code("500").description("500 message").build(),new ResponseBuilder()
            	.code("403").description("Forbidden!!!!!").build()))
        	.globalRequestParameters(Arrays.asList(new RequestParameterBuilder().
        	name("x-global-header-1").description("Remote User")
            	.in(ParameterType.HEADER).required(true)
            	.query(simpleParameterSpecificationBuilder 
                	-> simpleParameterSpecificationBuilder
            	.allowEmptyValue(false).model(modelSpecificationBuilder 
                	-> modelSpecificationBuilder
            	.scalarModel(ScalarType.STRING))).build(),
            	new RequestParameterBuilder().name("x-global-header-2")
            	.description("Impersonate User").in(ParameterType.HEADER).required(false)
            	.query(simpleParameterSpecificationBuilder 
                	-> simpleParameterSpecificationBuilder
                .allowEmptyValue(false).model(modelSpecificationBuilder 
                	-> modelSpecificationBuilder
                .scalarModel(ScalarType.STRING))).build()));
	}

	@Bean
	UiConfiguration uiConfig() {
    		return UiConfigurationBuilder.builder()
        	.operationsSorter(OperationsSorter.METHOD).build();
	}
    
	private ApiKey apiKey() {
    		return new ApiKey("apiKey", "Authorization", "header");
	}

	private SecurityContext securityContext() {
    		return SecurityContext.builder().
            	securityReferences(defaultAuth()).build();
	}

	private List<SecurityReference> defaultAuth() {
    		return Arrays.asList(new SecurityReference("apiKey", 
            	new AuthorizationScope[0]));
	}

	@Bean
	static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
		return new BeanPostProcessor() {
			@Override
        		public Object postProcessAfterInitialization(Object bean, 
                	String beanName) throws BeansException {
        		if (bean instanceof WebMvcRequestHandlerProvider) {
            			customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
            		}
            		return bean;
        	}
        
        private <T extends RequestMappingInfoHandlerMapping> 
        	void customizeSpringfoxHandlerMappings(List<T> mappings) {
        	List<T> copy = mappings.stream()
        		.filter(mapping -> mapping.getPatternParser() == null)
        		.collect(Collectors.toList());
        		mappings.clear();
        		mappings.addAll(copy);
        	}
        
        @SuppressWarnings("unchecked")
        private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
        	try {
            	Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                field.setAccessible(true);
                return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
            } catch (IllegalArgumentException | IllegalAccessException e) {
            	throw new IllegalStateException(e);
            }
        }
   };
  }
}

This library extensively uses googles guava library. For e.g. when you see newArrayList(…​) its actually the guava equivalent of creating an normal array list and adding item(s) to it.


4. Explanation

The Docket bean of our application can be configured to give us more control over the API documentation generation process.

globalRequestParameters method in the Docket builder accepts a list of parameters that will be part of every API operation in the generated Swagger specification.

Parameters can be instantiated using RequestParameterBuilder , another builder class, which has methods to accept name, description,type of Parameter , is Required.

If any specifications need to be added to the parameter, use SimpleParameterSpecificationBuilder.

It is not always desirable to expose the documentation for the entire API. We can restrict Swagger’s response by passing parameters to the apis() and paths() methods of the Docket class.

As seen above, RequestHandlerSelectors filter the API according to the base package , class annotation, and method annotations. But can also allows using the any or none predicates.

PathSelectors provides additional filtering with predicates, which scan the request paths of our application. We can use any(), none(), regex(), or ant().

In the example below, we will instruct Swagger to include only controllers from a particular package, with specific paths, using the ant() predicate:


We don't need to add @EnableSwagger2 or @EnableSwagger2WebMvc since we are using springfox swagger with spring boot.
After defining the Docket bean, its select() method returns an instance of ApiSelectorBuilder, which provides a way to control the endpoints exposed by Swagger.

We can configure predicates for selecting RequestHandlers with the help of RequestHandlerSelectors and PathSelectors. Using any() for both will make documentation for our entire API available through Swagger.

In application.properties file add this property: 
spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER to flip that property back to its previous default value. But this won't help if you use actuators which are not effected by that property.

Within Swagger’s response is a list of all controllers defined in our application. Clicking on any of them will list the valid HTTP methods (DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT).

Expanding each method provides additional useful data, such as response status, content-type, and a list of parameters. It is also possible to try each method using the UI.

Note: We've used the @EnableSwagger2WebMvc annotation to enable Swagger, as it has replaced the @EnableSwagger2 annotation in version 3 of the libraries.

Swagger also provides some default values in its response, which we can customize, such as “Api Documentation”, “Created by Contact Email”, and “Apache 2.0”.

To change these values, we can use the apiInfo(ApiInfo apiInfo) method — the ApiInfo class that contains custom information about the API:

  private ApiInfo apiInfo() {
	return new ApiInfo("SpringFox Demo APIs", 
    	"Demo APIs with spring boot rest api by usgin jdbi3", "1.0","Terms of service", 
        new Contact("YUTHADS", "https://yuthads.blogspot.com", "yuthads@mail.com"),
        "License of API", "API license URL", Collections.emptyList());
}
  
Swagger allows globally overriding response messages of HTTP methods through Docket’s globalResponses() method.

First, we need to instruct Swagger not to use default response messages. Suppose we want to override 500 and 403 response messages for all GET methods.

To achieve this, some code must be added to the Docket’s initialization block (original code is excluded for clarity):

.useDefaultResponseMessages(false)
.globalResponses(HttpMethod.GET,
	newArrayList(new ResponseBuilder().code("500").description("500 message").build(),
	new ResponseBuilder().code("403").description("Forbidden!!!!!").build()));
  

And most of the user tries to find HTML swagger document file using {host}/swagger-ui.html or {host}/swagger-ui those are now removed.

Use This{host}/swagger-ui/ to see the HTML document.


5. Demo & Testing

Let's start the application to generate the api document for the Spring Data REST APIs:



swagger-ui

Request API:


request api via swagger-ui

API Responded:


api responsed via swagger-ui


6. Conclusion 

Hope this article was helpful.

GitHub:


7. Ref.




Previous Post Next Post