How set SpringFox to show two (or more) versions of the Rest API using Spring Boot? - spring

I'm trying to figure out how manage two (or more) version of my API endpoints using Spring Fox.
To version my APIs, I'm using the Versioning through content negotiation, also know as Versioning using Accept header. The versions of each endpoint are controlled individually using the header information. Per example, for the version one I use the attribute produces:
#Override
#PostMapping(
produces = "application/vnd.company.v1+json")
public ResponseEntity<User> createUser(
For version two, I use:
#Override
#PostMapping(
produces = "application/vnd.company.v2+json",
consumes = "application/vnd.company.v2+json")
public ResponseEntity<User> createUserVersion2(
I not use consumes for the first (v1) version, so if the client use only application/json on the call the first version will be called by default.
I would like to show the two version on the Swagger UI. How to do that?

It's very simple. Just create one Docket for each version.
Example, the first version:
#Bean
public Docket customImplementation(
#Value("${springfox.documentation.info.title}") String title,
#Value("${springfox.documentation.info.description}") String description) {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo(title, description, "1.0"))
.groupName("v1")
.useDefaultResponseMessages(false)
.securitySchemes(newArrayList(apiKey()))
.pathMapping("/api")
.securityContexts(newArrayList(securityContext())).select()
.apis(e -> Objects.requireNonNull(e).produces().parallelStream()
.anyMatch(p -> "application/vnd.company.v1+json".equals(p.toString())))
.paths(PathSelectors.any())
.build();
}
And for version two:
#Bean
public Docket customImplementationV2(
#Value("${springfox.documentation.info.title}") String title,
#Value("${springfox.documentation.info.description}") String description) {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo(title, description, "2.0"))
.groupName("v2")
.select()
.apis(e -> Objects.requireNonNull(e).produces()
.parallelStream()
.anyMatch(p -> "application/vnd.company.v2+json".equals(p.toString())))
.build();
}
The secret here is filter the available endpoints by the produces attribute.
The Swagger-UI will show the two versions on the combo:
This code needs to be on a class annotated with #Configuration. You also need to enable the Swagger with #EnableSwagger2.

As mentioned by Dherik you can create Docket for each version. But to filter here I have tried using Predicate and custom controller annotations.
Configuration class annotated with #Configuration and #EnableSwagger2
import com.google.common.base.Predicate;
#Bean
public Docket apiV30() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("v30")
.select()
.apis(selectorV30())
.paths(PathSelectors.any()).build().apiInfo(apiEndPointsInfo());
}
private Predicate<RequestHandler> selectorV30(){
return new Predicate<RequestHandler>() {
#Override
public boolean apply(RequestHandler input) {
return input.findControllerAnnotation(SwaggerDocV30.class).isPresent();
}
};
}
#Bean
public Docket apiV31() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("v31")
.select()
.apis(selectorV31())
.paths(PathSelectors.any()).build().apiInfo(apiEndPointsInfo());
}
private Predicate<RequestHandler> selectorV31(){
return new Predicate<RequestHandler>() {
#Override
public boolean apply(RequestHandler input) {
return input.findControllerAnnotation(SwaggerDocV31.class).isPresent();
}
};
}
Custom Annotation class : SwaggerDocV30
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface SwaggerDocV30 {
}
Custom Annotation class : SwaggerDocV31
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface SwaggerDocV31 {
}
Finally annotate your controllers with #SwaggerDocV30 or #SwaggerDocV31
#SwaggerDocV30
#Controller
public class MyController extends AbstractController {}
Or
#SwaggerDocV31
#Controller
public class MyController extends AbstractController {}]

Related

SpringBoot Rest Controller Supporting both xml and json with JAXB Pojos + Custom Http Message Converters + Swagger-UI

Getting following Swagger error when I introduced custom message converters
Unable to render this definition
The provided definition does not specify a valid version field.
Please indicate a valid Swagger or OpenAPI version field. Supported version fields are swagger: "2.0" and those that match openapi: 3.0.n (for example, openapi: 3.0.0).
The error is misleading, I guess its getting confused with the converters. Any Ideas to get it fixed?
I followed - swagger-ui not working with custom XML ObjectMapper (no luck)
Background:
I have generated pojos from xsd(s) through xjc. And I have a rest endpoint which needs to support both xml and json for request/response
We got it working by following [spring documentation][1] section: 22.16.12 Message Converters
Here is what I added in MyConfig
#Configuration
#EnableWebMvc
public class MyConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(objectMapper()));
converters.add(new MappingJackson2XmlHttpMessageConverter(xmlMapper()));
}
#Bean
#Primary
public ObjectMapper objectMapper() {
return new Jackson2ObjectMapperBuilder()
.modulesToInstall(new JaxbAnnotationModule())
.build();
}
#Bean
public XmlMapper xmlMapper() {
return new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.defaultUseWrapper(false)
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.modulesToInstall(new JaxbAnnotationModule())
.createXmlMapper(true)
.build();
}
}
and my controller
// all the open api annotations //
#RequestMapping(value = "/run",
produces = {"application/json", "application/xml"},
consumes = {"application/json", "application/xml"},
method = RequestMethod.POST)
public ResponseEntity<MyResponse> run(#RequestBody MyRequest request) {
Ok its about which objectMapper. I had to clean up and remove '#primary' which made the trick in MyConfig
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter(new Jackson2ObjectMapperBuilder()
.modulesToInstall(new JaxbAnnotationModule())
.build());
}
#Bean
public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter() {
return new MappingJackson2XmlHttpMessageConverter(new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.defaultUseWrapper(false)
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.modulesToInstall(new JaxbAnnotationModule())
.createXmlMapper(true)
.build());
}

How can I add textbox/textfield in my swagger page built in Spring boot?

I am trying to auto-generate the swagger page for a RestAPI in Spring Boot using annotations.
Code of Controller:
#RestController
#Api(value="UserManagementAPI", produces = MediaType.APPLICATION_JSON_VALUE)
public class UserManagementController {
#RequestMapping("/")
public String index() {
return "Greetings from Spring Boot!";
}
#ApiOperation(value="add a pro",consumes="application/json")
#RequestMapping(value = "/getUser", method = RequestMethod.GET, produces="application/json")
public static List<UserDetails> getUser(#PathVariable(name="id") String id) throws UserException
{
return UserHelper.getUserByEmail(id);
}
Application.java
#SpringBootApplication
#EnableSwagger2
#Configuration
#ComponentScan({ "userManagement"})
#EnableAutoConfiguration
public class Application {
#Bean
public Docket simpleDiffServiceApi() {
return new Docket(DocumentationType.SWAGGER_2).groupName("userManagement").apiInfo(apiInfo()).select()
.apis(RequestHandlerSelectors.any())
// .paths(PathSelectors.any())
// Will also include the basic error controllers by default
.paths(Predicates.not(PathSelectors.regex("/error")))
// Exclude basic error controllers
.build().pathMapping("/");
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("Business Location Service")
.description("Spring Boot REST APIs for Business Location Service")
.contact(new Contact("EntSol-IoT (Niche Technology Delivery Group)", "http://somewebsite.com",
"some#mail.com"))
.version("1.0").build();
}
In the swagger page, I can see all my APIs. But there are more. It is showing all possible method type (e.g POST, GET, PUT etc.) though in Controller I only wrote GET method.
Another issue is that there is no Textbox in the swagger page under the API where I can search for the id. May be I am missing something. I have been trying to resolve it for past two days. But couldn't help myself. Thanks in advance.
I got the problem. Your getUser method is declared as static. Please remove static, for it to work.
public List<UserDetails> getUser(#PathVariable(name="id") String id) throws UserException { }

Is there a way I can stop springfox swagger from scanning the model classes?

I'm currently using Springfox Swagger to document my spring boot application with a Java config. My API starts in about 75 seconds, (it was originally 20 secs without Springfox) with the whole scanning process. I currently just need the controller information without any model info. Is there a way I can exclude model scanning from the startup process in order to make my API start faster? And are there any other ways to make it faster? I'm using swagger 1.2
There is a way to prevent Sprinfox framework from generating a Swagger model or parameter information of specified ignored types. You have to use the method ignoredParameterTypes in SwaggerSpringMvcPlugin or Docket class to let it know the types to be ignored.
Swagger 1 Example
Here is an example of Swagger 1 Java configuration with ignored types. It definitely had an impact on my application startup time.
#Configuration
#EnableSwagger
public class SwaggerConfiguration {
#Autowired
private SpringSwaggerConfig springSwaggerConfig;
#Bean
public SwaggerSpringMvcPlugin api() {
Class[] clazz = {MyClassA.class, MyClassB.class};
return new SwaggerSpringMvcPlugin(this.springSwaggerConfig)
.apiInfo(apiInfo())
...
.ignoredParameterTypes(clazz);
}
private ApiInfo apiInfo() {
...
}
}
Swagger 2 Example
Here is an example of Swagger 2 Java configuration with ignored types,
#Configuration
#EnableSwagger2
public class SwaggerConfiguration {
#Bean
public Docket api() {
Class[] clazz = {MyClassA.class, MyClassB.class};
return new Docket(DocumentationType.SWAGGER_2)
.groupName("my-group")
.select()
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo())
.ignoredParameterTypes(clazz);
}
private ApiInfo apiInfo() {
...
}
}
With swagger 3.0 #Hidden annotation is available which is present at package io.swagger.v3.oas.annotations
It can be used on top of the class or method to exclude the resource in swagger documentation.
Springfox Swagger2 acquire UI data through GET /v2/api-docs, which will mapping to springfox.documentation.swagger2.web.Swagger2Controller.getDocumentation().So you can just create a bean to take place of 'ServiceModelToSwagger2Mapper' with your sanning logic:
#Primary
#Component
class CustomServiceModelToSwagger2Mapper : ServiceModelToSwagger2MapperImpl() {
override fun mapDocumentation(from: Documentation?): Swagger? {
// scanning logics...
}
}
refer to my another related answer : https://stackoverflow.com/a/64057512/14332259

Hide return value from a Spring MVC controller in SpringFox 2

I have an async Spring MVC controller that returns Future. SpringFox 2 renders it as this:
How do I remove it? Thank you in advance!
I resolved the issue by adding the following method call to SprinfFox's Docket:
.genericModelSubstitutes(ResponseEntity.class, CompletableFuture.class)
Hope it will help someone.
It does not work with types.
So, let's suppose that this is the controller method:
public CompletableFuture<ResponseEntity<ResponseDTO>> getDTO() {
return CompletableFuture.completedFuture(ResponseEntity.ok(new ResponseDTO());
}
Adding just the configuration:
.genericModelSubstitutes(ResponseEntity.class, CompletableFuture.class)
.directModelSubstitute(CompletableFuture.class, java.lang.Void.class)
it will not work. The method needs to be modified to:
public CompletableFuture getDTO() {
return CompletableFuture.completedFuture(ResponseEntity.ok(new ResponseDTO());
}
Then the CompletableFuture definition will disappear from OpenAPI specs.
Found a better approach. I'm using springfox version 2.8.0. If you have a look at their docs (RTFM) you can have type classes resolved to POJO using a type adapter. An example for this configuration is like this:
import com.fasterxml.classmate.TypeResolver;
#Configuration
#EnableSwagger2
public class SpringFoxConfig {
#Autowired
private TypeResolver typeResolver;
#Bean
public Docket apiDocket() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("base package"))
.paths(PathSelectors.any())
.build()
.genericModelSubstitutes(ResponseEntity.class)
.alternateTypeRules(
newRule(typeResolver.resolve(CompletableFuture.class,
typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
typeResolver.resolve(WildcardType.class)))
.useDefaultResponseMessages(false)
.pathMapping("/");
}
And the controller method can be defined with types:
public CompletableFuture<ResponseEntity<ResponseDTO>> getDTO() {
return CompletableFuture.completedFuture(ResponseEntity.ok(new ResponseDTO());
}

Configured ObjectMapper not used in spring-boot-webflux

I have mixins configured in my objectmapperbuilder config, using the regular spring web controller, the data outputted according to the mixins.
However using webflux, a controller with a method returning a Flow or Mono have the data serialized like if the objectmapper a default one.
How to get webflux to enforce an objectmapper configuration to be used ?
sample config:
#Bean
JavaTimeModule javatimeModule(){
return new JavaTimeModule();
}
#Bean
Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.mixIn(MyClass.class, MyClassMixin.class);
}
I actually found my solution by stepping through the init code:
#Configuration
public class Config {
#Bean
JavaTimeModule javatimeModule(){
return new JavaTimeModule();
}
#Bean
Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.mixIn(MyClass.class, MyClassMixin.class);
}
#Bean
Jackson2JsonEncoder jackson2JsonEncoder(ObjectMapper mapper){
return new Jackson2JsonEncoder(mapper);
}
#Bean
Jackson2JsonDecoder jackson2JsonDecoder(ObjectMapper mapper){
return new Jackson2JsonDecoder(mapper);
}
#Bean
WebFluxConfigurer webFluxConfigurer(Jackson2JsonEncoder encoder, Jackson2JsonDecoder decoder){
return new WebFluxConfigurer() {
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(encoder);
configurer.defaultCodecs().jackson2JsonDecoder(decoder);
}
};
}
}
I translated the solution of #Alberto Galiana to Java and injected the configured Objectmapper for convenience, so you avoid having to do multiple configurations:
#Configuration
#RequiredArgsConstructor
public class WebFluxConfig implements WebFluxConfigurer {
private final ObjectMapper objectMapper;
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(
new Jackson2JsonEncoder(objectMapper)
);
configurer.defaultCodecs().jackson2JsonDecoder(
new Jackson2JsonDecoder(objectMapper)
);
}
}
Just implement WebFluxConfigurer and override method configureHttpMessageCodecs
Sample code for Spring Boot 2 + Kotlin
#Configuration
#EnableWebFlux
class WebConfiguration : WebFluxConfigurer {
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
configurer.defaultCodecs().jackson2JsonEncoder(Jackson2JsonEncoder(ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)))
configurer.defaultCodecs().jackson2JsonDecoder(Jackson2JsonDecoder(ObjectMapper()
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)))
}
}
Make sure all your data classes to be encoded/decoded have all its properties annotated with #JsonProperty even if property name is equal in class and json data
data class MyClass(
#NotNull
#JsonProperty("id")
val id: String,
#NotNull
#JsonProperty("my_name")
val name: String)
In my case, I was trying to use a customized ObjectMapper while inheriting all of the behavior from my app's default WebClient.
I found that I had to use WebClient.Builder.codecs. When I used WebClient.Builder.exchangeStrategies, the provided overrides were ignored. Not sure if this behavior is something specific to using WebClient.mutate, but this is the only solution I found that worked.
WebClient customizedWebClient = webClient.mutate()
.codecs(clientCodecConfigurer ->
clientCodecConfigurer.defaultCodecs()
.jackson2JsonDecoder(new Jackson2JsonDecoder(customObjectMapper)))
.build();
I have tried all the different solutions (#Primary #Bean for ObjectMapper, configureHttpMessageCodecs(), etc.). What worked for me at the end was specifying a MIME type. Here's an example:
#Configuration
class WebConfig: WebFluxConfigurer {
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
val encoder = Jackson2JsonEncoder(objectMapper, MimeTypeUtils.APPLICATION_JSON)
val decoder = Jackson2JsonDecoder(objectMapper, MimeTypeUtils.APPLICATION_JSON)
configurer.defaultCodecs().jackson2JsonEncoder(encoder)
configurer.defaultCodecs().jackson2JsonDecoder(decoder)
}
}

Resources