How to force a DTO class to be in Swagger models - java

I have a SpringBoot application with Swagger 3.17.1.
I have one abstract class AbstractDtoClass and one DTO class DtoClass that extends the first one.
I have several REST request definitions, and all returns a AbstractDtoClass object, none returns DtoClass.
As a result, the Swagger models, i.e. the DTO models I can find in "definitions" from /v2/api-docs, contains AbstractDtoClass but not DtoClass.
I would like DtoClass to be in the Swagger models too. How can I do that?
I have tried to put #SwaggerDefinition on the DtoClass definition.
I have tried to put #ApiModel(parent = AbstractDtoClass.class) on the DtoClass definition.
I have tried to put #ApiModel(subTypes = {DeclarationDto.class}, discriminator = "DeclarationDto") on the AbstractDtoClass definition even though I am not sure I am using discriminator properly.
Nothing have worked.
Can anyone help me please?

You can make use of following method to add additional models that are not part of any annotation or are perhaps implicit.
springfox.documentation.spring.web.plugins.Docket#additionalModels(ResolvedType first, ResolvedType... remaining)
Below is the sample DocketConfig,
#Configuration
#EnableSwagger2
public class SwaggerConfig {
private final TypeResolver typeResolver;
public SwaggerConfig(final TypeResolver typeResolver) {
this.typeResolver = typeResolver;
}
#Bean
public Docket docketApi() {
return new Docket(DocumentationType.SWAGGER_2)
.useDefaultResponseMessages(false)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.rmurugaian.service.pricing.server"))
.build()
.additionalModels(typeResolver.resolve(DummyDTO.class));
}

Related

how to exclude properties of super class when generating open API spec using swagger in java

We have a class which is inherited from third party library class.
While generating open API spec using swagger,
in schema all the properties of the third party class are also getting displayed along with our class properties.
Is there any way we can exclude all the properties from the third party library class in order to display only our class properties generated schema?
Include #ApiIgnore over the specific API to hide from Swagger Documentation.
Include #JsonIgnore over the specific property variable which needs to hide from Swagger Documentation.
I have a workaround for that
I added the un-needed properties into the child class and then used the #ApiModelProperty(hidden=true) on them
public class ExternalClass {
public String externalId;
...
}
public class OwnClass extends ExternalClass {
#ApiModelProperty(hidden=true)
public String externalId;
...
//our properties
}
Subclass ExternalClass like this
#Schema(subTypes = {
Subclass1.class, Subclass2.class, ...
})
public class OwnClass extends ExternalClass {
...
where Subclass1.class, Subclass2.class, ... extend from OwnClass
You should check the generated OpenAPI definition to understand which annotation to use. In my case, the superclass was included in the allOf property.
It was looking something like that:
User:
description: User
allOf:
- $ref: '#/components/schemas/AbstractResource_User_'
- required:
- name
type: object
properties:
...
To exclude it I had to explicitly add the #Schema(allOf = {}) annotation to the top of my User class definition:
#Schema(allOf = {})
public class User extends AbstractResource<User> {
...
}
This excluded the AbstractResource class from the OpenAPI schema.
You can pass Class type in ignoredParameterTypes method to exclude the classes you don’t want to include.
#Configuration
public class SpringFoxConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.ignoredParameterTypes(ExcludeClass.class);
}
}

How to document additional models in Swagger for an inaccessible Controller?

I have a controller that I have to keep generic by having it accept a String as #RequestBody and return a String, i.e., String processRequest(#RequestBody String json) {...}
I don't have control over the source code of that controller, but I can get to it programmatically.
The real objects that are going to be passed in and returned are defined elsewhere in a series of request messages: RequestMessage1.java, RequestMessage2.java, etc. Responses are likewise: Response1.java, Response2.java1.
The controller also hands off the processing of these requests to a Processor that looks something like this (Request1Processor.java): Response1 process(RequestMessage1 message).
My question is this.
Is there a way to configure swagger such that it exposes the REST controller class's endpoint, i.e, processRequest, but shows all these Processor classes with their inputs and outputs as the documentation for that controller?
I saw as part of documentation the ability to add models that are not "reachable". I tried the method that's in documentation like this:
#Autowired
private TypeResolver typeResolver;
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.additionalModels(typeResolver.resolve(Date.class));
}
However, the additional Date model did not show up in swagger-ui.html.
What am I doing wrong here?
Also, is there a way to somehow show that RequestMessage1 type will have a response with Response1?
Date class was an unfavourable example to test because it is treated as a string.
Data Types
...
string (this includes dates and files)
Try it again with real models you want to document additionally:
#Bean
public Docket api(TypeResolver typeResolver) {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.additionalModels(typeResolver.resolve(RequestMessage1.class, Response1.class)));
}
An alternative solution is to extend the already existing docket object instead of creating a new one.
Here is a solution:
import com.fasterxml.classmate.TypeResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.spring.web.plugins.Docket;
#Configuration
public class SwaggerConfiguration {
private final TypeResolver typeResolver;
#Autowired
public SwaggerConfiguration(TypeResolver typeResolver) {
this.typeResolver = typeResolver;
}
#Autowireda
public void createAdditionalModelDocumentation(Docket docket) {
docket.additionalModels(typeResolver.resolve(RequestMessage1.class),
typeResolver.resolve(Response1.class));
}
}

How to hide repository-controller from Swagger UI

I am using spring-boot-starter-parent 1.3.3.RELEASE. I am unable to disable repository controller in Swagger UI
I have disabled the other unwanted endpoints using this link.
How to disable repository-controller from swagger UI?? kindly provide your inputs
If use the follow, it returns only the search repository which written by me but I need the entity endpoints also. Those entities endpoints will return by spring defaults.
.apis(RequestHandlerSelectors.basePackage("bla.blablah.bla"))
Kindly refer the image:
When initializing your Docket in your Application class, you can filter package names easily, therefore it will create your api for only given base package:
#Bean
public Docket swaggerSpringMvcPlugin(){
return new Docket( DocumentationType.SWAGGER_2 )//
.select().apis( RequestHandlerSelectors.basePackage( "com.blabla.bla" ) )//
.build();
}
Another solution:
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
.apis(Predicates.not(RequestHandlerSelectors.basePackage("org.springframework.boot")))
.build();
Another solution that negates a specific package:
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
.apis(RequestHandlerSelectors.basePackage("org.springframework.boot").negate())
.build();
I solved the same issue by creating my own Predicate and adding them to the Docket configuration. You can get the Group name (repository-controller) from RequestHandler Object.
My code:
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(GroupNameFilter())
.build();
}
private Predicate<RequestHandler> GroupNameFilter(){
return new Predicate<RequestHandler>() {
#Override
public boolean apply(RequestHandler input) {
return !input.groupName().equals("repository-controller");
}
};
}

Api annotation's description is deprecated

In Swagger, the #Api annotation's description element is deprecated.
Deprecated.
Not used in 1.5.X, kept for legacy support.
Is there a newer way of providing the description?
I found two solutions for Spring Boot application:
1. Swagger 2 based:
Firstly, use the tags method for specify the tags definitions in your Docket bean:
#Configuration
#EnableSwagger2
public class Swagger2Config {
public static final String TAG_1 = "tag1";
#Bean
public Docket productApi() {
return new Docket(DocumentationType.SWAGGER_2).select()
.apis(RequestHandlerSelectors.basePackage("my.package")).build()
.tags(new Tag(TAG_1, "Tag 1 description."))
// Other tags here...
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("My API").version("1.0.0").build();
}
}
After, in RestController just add the #Api annotation with one (or more) of the your tags:
#Api(tags = { SwaggerConfig.TAG_1 })
#RestController
#RequestMapping("tag1-domain")
public class Tag1RestController { ... }
2. Swagger 3 based (OpenAPI):
Similarly, use the addTagsItem method for specify the tags definitions in your OpenAPI bean:
#Configuration
public class OpenApiConfig {
public static final String TAG_1 = "tag1";
#Bean
public OpenAPI customOpenAPI() {
final Info info = new Info()
.title("My API")
.description("My API description.")
.version("1.0.0");
return new OpenAPI().components(new Components())
.addTagsItem(createTag(TAG_1, "Tag 1 description."))
// Other tags here...
.info(info);
}
private Tag createTag(String name, String description) {
final Tag tag = new Tag();
tag.setName(name);
tag.setDescription(description);
return tag;
}
}
Finally, in RestController just add the #Tag annotation:
#Tag(name = OpenApiConfig.TAG_1)
#RestController
#RequestMapping("tag1-domain")
public class Tag1RestController { ... }
This is the correct way to add description to your Swagger API documentation for Swagger v1.5:
#Api(tags = {"Swagger Resource"})
#SwaggerDefinition(tags = {
#Tag(name = "Swagger Resource", description = "Write description here")
})
public class ... {
}
The reason why it's deprecated is that previous Swagger versions (1.x) used the #Api description annotation to group operations.
In the Swagger 2.0 specification, the notion of tags was created and made a more flexible grouping mechanism. To be API compliant, the description field was retained so upgrades would be easy, but the correct way to add a description is though the tags attribute, which should reference a #Tag annotation. The #Tag allows you to provide a description and also external links, etc.
I tried above solutions but they didn't work for me.
To add a title and description to the documentation you create ApiInfo and Contact objects like in example below.
Then you simply add apiInfo object to your Swagger Docket.
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
#EnableSwagger2
#Configuration
public class SwaggerConfig {
private Contact contact = new Contact("", "", "");
private ApiInfo apiInfo = new ApiInfo(
"Backoffice API documentation",
"This page documents Backoffice RESTful Web Service Endpoints",
"1.0",
"",
contact,
"",
"");
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage(
PaymentsController.class.getPackage().getName()
))
.paths(PathSelectors.ant("/api/v1/payments" + "/**"))
.build()
.useDefaultResponseMessages(false)
.globalOperationParameters(
newArrayList(new ParameterBuilder()
.name("x-authorization")
.description("X-Authorization")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(false)
.build()));
}
}
Above code produces a description like in a screenshot below.
I too wondered what to do about uses of the deprecated description (showing up as warnings in my IDE).
Well, on closer inspection it turned out that description is not used anywhere in Swagger UI. After that the solution (in our case*) became clear: simply remove those descriptions.
(*In our codebase, with clean class and method names etc, there was certainly no need for such "API descriptions" for the reader of the code. I would have tolerated having these bits of Swagger-related noise in the codebase if they added some value in Swagger UI, but since they didn't, the only sensible thing was to throw them away.)
I found that the following works by combining both the #Api and #Tag annotations building off of this answer.
The value within the tags field of the #Api annotation needs to match the value within the name field of the #Tag annotation.
#Api(tags = "Controller API")
#Tag(name = "Controller API", description = "This controller performs API operations")
public class ReportStatusConsumerController {
}
An old question but may help using swagger 3
#Configuration
#EnableSwagger2
public class SwaggerConfig {
// Swagger configuration
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo( this.apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("API Reference").version("1.0.0")
.description("something")
.license("Apache 2.0")
.build();
}
public void addResouceHandler(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}

How to configure a default #RestController URI prefix for all controllers?

I know you can set the server.contextPath in application.properties to change the root context.
Also, I can add an additional context in the application config for Spring Boot like the following example (in Groovy) to add an "/api" to the URL mappings of the root context:
#Bean
ServletRegistrationBean dispatcherServlet() {
ServletRegistrationBean reg = new ServletRegistrationBean(new DispatcherServlet(), "/")
reg.name = "dispatcherServlet"
reg.addInitParameter("contextConfigLocation", "")
reg.addUrlMappings("/api/*")
reg.loadOnStartup = 2
reg
}
}
I am trying to have a separate base URI "/api" specifically for web service calls, that I can leverage for security, etc. However using the above approach will mean that any of my URIs, web service or not, can be reached with "/" or "/api", and provides no concrete segregation.
Is anyone aware of a better approach to set a base path for all #RestController(s) using configuration, without having to formally prefix every controller with /api/? If I am forced to manually prefix the URI for each controller, it would be possible to mistakenly omit that and bypass my security measures specific to web services.
Here is a reference in Stack Overflow to the same type of question, which was never completely answered:
Spring Boot: Configure a url prefix for RestControllers
In continuation to the currently accepted solution the github issue addresses the same.
Spring 5.1 and above you can implement WebMvcConfigurer and override configurePathMatch method like below
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api",
HandlerTypePredicate.forAnnotation(RestController.class));
}
}
Now all the #RestControllers will have /api as the prefix path alongside the path configured.
Official Documentation
There's a new solution to solve this kind of problem available since Spring Boot 1.4.0.RC1 (Details see https://github.com/spring-projects/spring-boot/issues/5004)
The solution of Shahin ASkari disables parts of the Auto configuration, so might cause other problems.
The following solution takes his idea and integrates it properly into spring boot. For my case I wanted all RestControllers with the base path api, but still serve static content with the root path (f.e. angular webapp)
Edit: I summed it up in a blog post with a slightly improved version see https://mhdevelopment.wordpress.com/2016/10/03/spring-restcontroller-specific-basepath/
#Configuration
public class WebConfig {
#Bean
public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerMapping() {
return new WebMvcRegistrationsAdapter() {
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
private final static String API_BASE_PATH = "api";
#Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
Class<?> beanType = method.getDeclaringClass();
RestController restApiController = beanType.getAnnotation(RestController.class);
if (restApiController != null) {
PatternsRequestCondition apiPattern = new PatternsRequestCondition(API_BASE_PATH)
.combine(mapping.getPatternsCondition());
mapping = new RequestMappingInfo(mapping.getName(), apiPattern,
mapping.getMethodsCondition(), mapping.getParamsCondition(),
mapping.getHeadersCondition(), mapping.getConsumesCondition(),
mapping.getProducesCondition(), mapping.getCustomCondition());
}
super.registerHandlerMethod(handler, method, mapping);
}
};
}
};
}
}
Also You can achieve the same result by configuring WebMVC like this:
#Configuration
public class PluginConfig implements WebMvcConfigurer {
public static final String PREFIX = "/myprefix";
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix(PREFIX, c -> c.isAnnotationPresent(MyCustomAnnotation.class));
}
}
Implement WebMvcConfigurer on any #Configuration class.
Override configurePathMatch method.
You can do many useful things with PathMatchConfigurer e.g. add prefix for several classes, that satisfy predicate conditions.
I had the same concern and was not a fan of the Spring EL option due to the issues documented and I wanted the prefix to be tightly controlled in the controllers but I did not want to depend on the developers doing the right thing.
There might be a better way these days but this is what I did. Can you guys see any downsides, I am still in the process of testing any side-effects.
Define a custom annotation.
This allows a developer to explicitly provide typed attributes such as int apiVersion(), String resourceName(). These values would be the basis of the prefix later.
Annotated rest controllers with this new annotation
Implemented a custom RequestMappingHandlerMapping
In the RequestMappingHandlerMapping, I could read the attribute of the custom annotation and modify the final RequestMappingInfo as I needed. Here are a few code snippets:
#Configuration
public class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new MyCustomRequestMappingHandlerMapping();
}
}
And in the MyCustomRequestMappingHandlerMapping, overwrite the registerHandlerMethod:
private class MyCustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private Logger myLogger = LoggerFactory.getLogger(MyCustomRequestMappingHandlerMapping.class);
public MyCustomRequestMappingHandlerMapping() {
super();
}
#Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
// find the class declaring this method
Class<?> beanType = method.getDeclaringClass();
// check for the My rest controller annotation
MyRestController myRestAnnotation = beanType.getAnnotation(MyRestController.class);
if (myRestAnnotation != null) {
// this is a My annotated rest service, lets modify the URL mapping
PatternsRequestCondition oldPattern = mapping.getPatternsCondition();
// create a pattern such as /api/v${apiVersion}/${resourceName}
String urlPattern = String.format("/api/v%d/%s",
myRestAnnotation.apiVersion(),
myRestAnnotation.resourceName());
// create a new condition
PatternsRequestCondition apiPattern =
new PatternsRequestCondition(urlPattern);
// ask our condition to be the core, but import all settinsg from the old
// pattern
PatternsRequestCondition updatedFinalPattern = apiPattern.combine(oldPattern);
myLogger.info("re-writing mapping for {}, myRestAnnotation={}, original={}, final={}",
beanType, myRestAnnotation, oldPattern, updatedFinalPattern);
mapping = new RequestMappingInfo(
mapping.getName(),
updatedFinalPattern,
mapping.getMethodsCondition(),
mapping.getParamsCondition(),
mapping.getHeadersCondition(),
mapping.getConsumesCondition(),
mapping.getProducesCondition(),
mapping.getCustomCondition()
);
}
super.registerHandlerMethod(handler, method, mapping);
}
}
Slightly less verbose solution which doesn't duplicate the logic of checking the annotation, but only changes the mapping path:
private static final String API_PREFIX = "api";
#Bean
WebMvcRegistrationsAdapter restPrefixAppender() {
return new WebMvcRegistrationsAdapter() {
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
#Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo mappingForMethod = super.getMappingForMethod(method, handlerType);
if (mappingForMethod != null) {
return RequestMappingInfo.paths(API_PREFIX).build().combine(mappingForMethod);
} else {
return null;
}
}
};
}
};
}
Side effects
Your error controller will also be mapped under /api/error, which breaks error handling (DispatcherServlet will still redirect errors to /error without prefix!).
Possible solution is to skip /error path when adding /api prefix in the code above (one more "if").
Someone has filed an issue in the Spring MVC Jira and come up with a nice solution, which I am now using. The idea is to use the Spring Expression Language in the prefix placed in each RestController file and to refer to a single property in the Spring Boot application.properties file.
Here is the link of the issue: https://jira.spring.io/browse/SPR-13882

Categories