Spring. Concut URL path in custom RequestMapping annotation - java

I want to hide some peace of URL path in custom #RequestMapping.
Tried somiting like this:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#RequestMapping(value = "/api/v1/", produces = "application/json" )
#Description(value = "API v1 Controller")
public #interface RequestMappingApiV1 {
#AliasFor(annotation = RequestMapping.class, attribute = "path")
String[] path() default {};
}
}
#RestController
#RequestMappingApiV1(path = "items")
public class ItemController implements ItemV1Api { }
Unfortunately it looks like the path attribute is overwriting api/v1. Instead of http://hostname/api/v1/items i get http://hostname/items
Are there other paths with custom annotation?

Related

How to hide endpoints from OpenAPI documentation with Springdoc

Springdoc automatically generates a API documentation for all handler methods. Even if there are no OpenAPI annotations.
How can I hide endpoints from the API documentation?
The #io.swagger.v3.oas.annotations.Hidden annotation can be used at the method or class level of a controller to hide one or all endpoints.
(See: https://springdoc.org/faq.html#how-can-i-hide-an-operation-or-a-controller-from-documentation)
Example:
#Hidden // Hide all endpoints
#RestController
#RequestMapping(path = "/test")
public class TestController {
private String test = "Test";
#Operation(summary = "Get test string", description = "Returns a test string", tags = { "test" })
#ApiResponses(value = { #ApiResponse(responseCode = "200", description = "Success" ) })
#GetMapping(value = "", produces = MediaType.TEXT_PLAIN_VALUE)
public #ResponseBody String getTest() {
return test;
}
#Hidden // Hide this endpoint
#PutMapping(value = "", consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseStatus(HttpStatus.OK)
public void setTest(#RequestBody String test) {
this.test = test;
}
}
Edit:
Its also possible to generate the API documentation only for controllers of specific packages.
Add following to your application.properties file:
springdoc.packagesToScan=package1, package2
(See: https://springdoc.org/faq.html#how-can-i-explicitly-set-which-packages-to-scan)
If you are working with Swagger Api and you want to hide specific endpoint then use #ApiOperation(value = "Get Building",hidden=true) on that endpoint...hidden attribute should be true.
#RestController
#Api(tags="Building")
#RequestMapping(value="/v2/buildings")
public class BuildingsController {
#ApiOperation(value = "Get Building",hidden=true)
#GetMapping(value = "/{reference}")
public Account getBuildings(#PathVariable String reference) {
....
}
Its also possible to generate the API doc only for specific Path.
Add following to your application.yml file:
springdoc:
paths-to-match: /api/**, /v1

How to change declared body type from String to custom DTO type in Swagger using SpringBoot and SpringFox

I have a rest controller with one method. This method takes one String argument annotated as #RequestBody. For some reason not mentioned here, I'm forced to use type String and manually convert it to TestDTO. From the API's consumer point of view body is type of TestDTO and I want to show this type in SwaggerUI.
Unfortunately (which is quite obvious) swagger shows that body is type of String. Look at the picture below.
What I want to achieve is to have String body in java code and TestDTO in swagger code. How can I force Swagger to show it? I tried to find annotations and its properties, but failed.
Rest controller code below:
#RestController
#Api(tags = { "test" }, description = "test related resources")
public class TestController {
#Autowired
ObjectMapper mapper;
#RequestMapping(path = "/test", method = RequestMethod.POST)
public void confirm(#RequestBody String requestBody) throws IOException {
//do sth with body
TestDTO dto = mapper.readValue(requestBody, TestDTO.class);
//do sth with dto
}
}
class TestDTO{
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
I figured it out. Two changes need to be made.
First, like in #Dave Pateral's answer #ApiImplicitParams must be added
#RestController
#Api(tags = { "test" }, description = "test related resources")
public class TestController {
#Autowired
ObjectMapper mapper;
#ApiImplicitParams({
#ApiImplicitParam(name = "requestBody", required = true,
dataType = "TestDTO", paramType = "body")
})
#RequestMapping(path = "/test", method = RequestMethod.POST)
public void confirm(#RequestBody String requestBody) throws IOException {
//do sth with body
TestDTO dto = mapper.readValue(requestBody, TestDTO.class);
//do sth with dto
}
}
And then implicit Model must be registered in the docket, minimal working example below
#Configuration
public class SwaggerConfiguration {
#Autowired
private TypeResolver typeResolver;
#Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.additionalModels(typeResolver.resolve(TestDTO.class));
}
}
And the result is
Try put this annotation on your method:
#ApiImplicitParam(name = "test", value = "testDTO", required = true, dataType = "TestDTO")

How to set defaults for #RequestMapping?

I'm using #RestController with #RequestMapping annotations to define all of my servlets with spring-mvc.
Question: how can I define some defaults for those annotation, so I don't have to repeat the same configuration regarding eg consumes and produces?
I'd like to always apply the following config, without having to repeat it on each path:
#GetMapping(produces = {APPLICATION_XML_VALUE, APPLICATION_JSON_VALUE})
#PostMapping(
consumes = {APPLICATION_XML_VALUE, APPLICATION_JSON_VALUE},
produces = {APPLICATION_XML_VALUE, APPLICATION_JSON_VALUE})
Probably it's easiest to just create a custom #RestController annotation and use that on classlevel. Then I only have to repeat the #PostMapping(consumes...) mappings:
#Target(ElementType.TYPE)
#Retention(value=RUNTIME)
#RestController
#RequestMapping(produces = {APPLICATION_JSON_VALUE, APPLICATION_XML_VALUE})
public #interface DefaultRestController {
}
Usage like:
#DefaultRestController
public class MyServlet {
#GetMapping("/getmap") //inherits the 'produces' mapping
public void getmap() {
}
#PostMapping("/postmap", consumes = {APPLICATION_JSON_VALUE, APPLICATION_XML_VALUE})
public void postmap() {
}
}
Better than nothing.
The target for RequestMapping annotation could be either a method or class. It can be used instead of GetMapping and PostMapping annotations that target only methods.
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/GetMapping.html
Specifically, #GetMapping is a composed annotation that acts as a
shortcut for #RequestMapping(method = RequestMethod.GET).
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/PostMapping.html
Specifically, #PostMapping is a composed annotation that acts as a
shortcut for #RequestMapping(method = RequestMethod.POST).
Assuming your Controller Name is HelloController, add the RequestMapping annotation with appropriate methods at Class level so that it applies automatically to all the paths.
#Controller
#RequestMapping(method={RequestMethod.GET,RequestMethod.POST}, consumes = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE },produces = { MediaType.APPLICATION_XML_VALUE,MediaType.APPLICATION_JSON_VALUE },)
class HelloController{
}
This confgiuration can be overridden by annotating it in individual methods.
You can put an annotation on class. Here is an example:
#RestController
#RequestMapping(
consumes = {APPLICATION_XML_VALUE, APPLICATION_JSON_VALUE},
produces = {APPLICATION_XML_VALUE, APPLICATION_JSON_VALUE}
)
public class MyClass {
// after that you don't have to put any
// #RequestMapping default values before methods
}

Spring request mapping custom annotations - ambiguous mapping

I have the following Spring MVC Controller:
#RestController
#RequestMapping(value = "my-rest-endpoint")
public class MyController {
#GetMapping
public List<MyStuff> defaultGet() {
...
}
#GetMapping(params = {"param1=value1", "param2=value2"})
public MySpecificStuff getSpecific() {
...
}
#GetMapping(params = {"param1=value1", "param2=value3"})
public MySpecificStuff getSpecific2() {
return uiSchemas.getRelatedPartyUi();
}
}
What I need is to make it more generic using custom annotations:
#RestController
#RequestMapping(value = "my-rest-endpoint")
public class MyController {
#GetMapping
public List<MyStuff> defaultGet() {
...
}
#MySpecificMapping(param2 = "value2")
public MySpecificStuff getSpecific() {
...
}
#MySpecificMapping(param2 = "value3")
public MySpecificStuff getSpecific2() {
return uiSchemas.getRelatedPartyUi();
}
}
I know that Spring meta annotations could help me with that.
So I define the annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#RequestMapping(method = RequestMethod.GET, params = {"param1=value1"})
public #interface MySpecificMapping {
String param2() default "";
}
That alone won't do the trick.
So I add an interceptor to deal with that "param2":
public class MyInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// get annotation of the method
MySpecificMapping mySpecificMapping = handlerMethod.getMethodAnnotation(MySpecificMapping.class);
if (mySpecificMapping != null) {
// get the param2 value from the annotation
String param2 = mySpecificMapping.param2();
if (StringUtils.isNotEmpty(param2)) {
// match the query string with annotation
String actualParam2 = request.getParameter("param2");
return param2 .equals(actualParam2);
}
}
}
return true;
}
}
And include it into the Spring configuration of course.
That works fine but only if I have one such custom mapping per controller.
If I add two methods annotated with #MySpecificMapping even having different values of "param2" then I get an "ambiguous mapping" error of the application start:
java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'myController' method
public com.nailgun.MySpecificStuff com.nailgun.MyController.getSpecific2()
to {[/my-rest-endpoint],methods=[GET],params=[param1=value1]}: There is already 'myController' bean method
public com.nailgun.MySpecificStuff com.nailgun.MyController.getSpecific() mapped.
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.assertUniqueMethodMapping(AbstractHandlerMethodMapping.java:576)
- Application startup failed
I understand why it happens.
But can you help me to give Spring a hint that those are two different mappings?
I am using Spring Boot 1.4.3 with Spring Web 4.3.5
#AliasFor is annotation for do things like this.
Here is an example of custom annotation with #RequestMapping
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
#RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public #interface JsonGetMapping {
#AliasFor(annotation = RequestMapping.class, attribute = "value")
String value() default "";
}
and also example of use
#JsonGetMapping("/category/{categoryName}/books")
public List<Book> getBooksByCategory(#PathVariable("categoryName") String categoryName){
return bookRepository.getBooksByCategory(categoryName);
}
You can not bind annotations in the stack with their params and Spring will consider these two methods as methods with equal #RequestMapping.
But you could try make a trick: embed somehow your custom annotation enhancer before mapping builder and perform there annotations replacing:
Get all methods with annotation #MySpecificMapping:
MySpecificMapping myMapping = ...;
Read #RequestMapping annotation for each such method, let say it will be
RequestMapping oldMapping = ...;
Create new instance of the #RequestMapping class:
RequestMapping newMapping = new RequestMapping() {
// ... rest methods
#Override
public String[] params() {
// here merge params from old and MySpecificMapping:
String[] params = new String[oldMapping.params().length + 1];
// todo: copy old one
// ...
params[params.length-1] = myMapping.param2();
return params;
}
}
Forcly assign this new newMapping to each method correspondingly instead of oldMapping.
This is quite tricky and complex, but this is only one way to achieve what you want, I believe.
I think the best way around this is to move your #RequestMapping annotation to the method level instead of the class level.
The error Spring is giving you is because Spring is binding multiple handlers on one path which is invalid. Maybe give us an example of the URL's you'd like to expose so we have a better overview of what you're trying to build.

Spring Controller: Custom annotation with combined Spring annotations does not work

I have properly working Spring Controller, where I have a method mapped in a following way
#RequestMapping(
value = "/users"
consumes = MimeTypeUtils.APPLICATION_JSON_VALUE,
produces = MimeTypeUtils.APPLICATION_JSON_VALUE,
method = RequestMethod.GET)
#ResponseBody
#ResponseStatus(HttpStatus.OK)
public UserResponse retrieveUsers() {
return new UserResponse();
}
#RequestMapping(
value = "/contracts"
consumes = MimeTypeUtils.APPLICATION_JSON_VALUE,
produces = MimeTypeUtils.APPLICATION_JSON_VALUE,
method = RequestMethod.GET)
#ResponseBody
#ResponseStatus(HttpStatus.OK)
public ContractResponse retrieveContracts() {
return new ContractResponse();
}
This works fine, GET requests are served as accepted, and in case of for example POST I am receiving proper 405 status code.
Now I want to introduce custom combined annotation, not to have the same bunch of annotations in each and every method.
My custom annotation looks like,
#RequestMapping(
consumes = MimeTypeUtils.APPLICATION_JSON_VALUE,
produces = MimeTypeUtils.APPLICATION_JSON_VALUE,
method = RequestMethod.GET)
#ResponseBody
#ResponseStatus(HttpStatus.OK)
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Get {}
Accordingly, I change the method to
#Get
#RequestMapping(value = "/users")
public UserResponse retrieveUsers() {
return new UserResponse();
}
In this case I can see that whatever type of request I send to /users it is served properly. For example even if I do POST, I see response. So the #RequestMapping does not work properly.
What am I doing wrong here? Is it possible to make controllers behave properly with custom combined annotation?
I suspect that the #RequestMapping(value = "/users") on UserResponse#retrieveUsers replaces the #RequestMapping on the #Get interface.
See if this works:
#RequestMapping(
consumes = MimeTypeUtils.APPLICATION_JSON_VALUE,
produces = MimeTypeUtils.APPLICATION_JSON_VALUE,
method = RequestMethod.GET)
#ResponseBody
#ResponseStatus(HttpStatus.OK)
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Get {
#AliasFor(annotation = RequestMapping.class, attribute = "value")
String[] value() default {};
}
#Get(value = "/users")
public UserResponse retrieveUsers() {
return new UserResponse();
}
You may be interested in GetJson in the spring-composed project.
Note that #AliasFor was only released with Spring 4.2. It's not available in earlier versions.
You overwrite the #RequestMapping annotation you had set up in your #Get annotation. Therefore it specifies only the value part of the request mapping, leaving all other properties to default.

Categories