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

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.

Related

How to return json/xml from Spring Boot #Controller?

I'm trying to return JSON/XML from a function of my Controller. First I was using #RestController and it worked good, but now I need to change to #Controller, because I will use also some other functions and pass there some objects for my view.
#Controller
#RequestMapping("/game")
public class ViewController {
#RequestMapping(value = "/statistic", method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public GamerData[] getStatistic() {
RestTemplate restTemplate = new RestTemplate();
try {
String uri_get_statistic = "http://localhost:8081/statistic/";
ResponseEntity<GamerData[]> response = restTemplate.getForEntity(uri_get_statistic, GamerData[].class);
GamerData[] statisticData = response.getBody();
return statisticData;
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
After I've changed to Controller I get error 404 not found. With RestController I've got json. (GamerData is just a class with 2 simple fields (int and String), setters, getters, consructor).
UPDATE:
I've added #ResponseBody to my function, but now I have Internal Server error
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class [Lgame.mainservice.mvc.GamerData;] with preset Content-Type 'null']
try to add #ResponseBody like
public #ResponseBody GamerData[] getStatistic....
#RequestMapping(value = "/statistic", method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#ResponseBody // add this one
public GamerData[] getStatistic() {}
Thank you all for answers, I've added #ResponceBody annotation to my function and removed "produces" part from #RequestMapping annotation and my function works great:
#RequestMapping(value = "/statistic", method = RequestMethod.GET)
#ResponseBody
public GamerData[] getStatistic() {
//function code
}

Spring 5 consume multiple MediaType

I'm using Spring boot 1.5.8.RELEASE
I have an endpoint and would like to consume multiple MediaType.
In particular application/x-www-form-urlencoded and application/json type.
At the moment I have the below code. It works for application/x-www-form-urlencoded, but doesn't work for application/json.
#RequestMapping(path = "/abc", method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE},
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(value = HttpStatus.OK)
public ResponseEntity<MyResponse> validate(#Valid #ModelAttribute MyDetails details) {
return something();
}
I tried to put a content negotiator in my configuration, but still doesn't work.
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("x-www-form-urlencoded", MediaType.APPLICATION_FORM_URLENCODED);
}
How can I allow the endpoint to accept these 2 media types?
exchange {} to [] for consumes
Try it:
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
reference: Spring RequestMapping - Baeldung

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
}

Swagger Controller Mappings

With Swagger-UI I am trying to display methods that have the same base URL underneath one Swagger container regardless of which controller they fall under. Imagine this code.
// Current Swagger Config Class
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(getPaths())
.build()
.apiInfo(getApiInfo());
}
private Predicate<String> getPaths(){
return or(regex("/attributes.*"),
regex("/other.*"));
}
// Controller 1
#RestController
#RequestMapping("/attributes")
#Api(value = "User Attribute Services", description = "User Attributes API")
public class UserAttributesController {
#Autowired
private UserAttributesService userService;
#Autowired
private UserAttributeValidator userAttributeValidator;
#RequestMapping(value = "/user/search", method = RequestMethod.POST, produces = "application/json")
public List<User> searchUser(#RequestBody List<UserDTO> userDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/user/insert", method = RequestMethod.POST)
public boolean insertUser(#RequestBody List<UserDTO> userDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/user/update{id}", method = RequestMethod.PATCH)
public boolean updateUser(#PathVariable id, #RequestBody UserDTO updateDetails){
// meaningless implementation ...
}
#RequestMapping(value = "/user/delete/{id}", method = RequestMethod.DELETE)
public boolean deleteUser(#PathVariable id){
// meaningless implementation ...
}
}
// Controller 2
#RestController
#RequestMapping("/attributes")
#Api(value = "Tech Attribute Services", description = "Tech Attributes API")
public class TechAttributesController {
#Autowired
private TechAttributesService techService;
#Autowired
private TechAttributeValidator techAttributeValidator;
#RequestMapping(value = "/tech/search", method = RequestMethod.POST, produces = "application/json")
public List<Tech> searchTech(#RequestBody List<TechDTO> techDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/tech/insert", method = RequestMethod.POST)
public boolean insertTech(#RequestBody List<TechDTO> techDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/tech/update{id}", method = RequestMethod.PATCH)
public boolean updateTech(#PathVariable id, #RequestBody TechDTO updateDetails){
// meaningless implementation ...
}
#RequestMapping(value = "/tech/delete/{id}", method = RequestMethod.DELETE)
public boolean deleteTech(#PathVariable id){
// meaningless implementation ...
}
}
My application swagger page is displaying the attributes underneath seperate containers. Eventhough I specified the regex path of '/attributes' in my Swagger Configuration the fact that these are in different controllers is overriding what I want. If I put all these methods into 1 controller I get the desired result, but this is only a subset of the attribute classes I have and I do not want a massive controller that spans over 1k lines.
Extra: If you noticed the services are almost identical for the attributes. I have been trying to find a clever way of declaring them once and simply overriding the method declations instead of redefining similiar services over again, but because the #RequestMapping annotation needs to be a constant value I could not find a way to change the annotation depending on the implementation rather than the declaration.
Swagger-UI Picture

Request Mapping with Multiple Wildcards

I want to have two endpoints that have wild cards in the #RequestMapping
#RequestMapping(value="/**", method = { RequestMethod.GET}, produces = "application/json")
#RequestMapping(value="/**/versions/{versionId}", method = { RequestMethod.GET}, produces = "application/json")
When I perform a request that should go to /**/versions/{versionId} it is preferring the /** endpoint over the /**/versions/{versionId} endpoint even though the request should match.
I am using:
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Brixton.SR2</version>
</parent>
I think you just have to change the order of your #RequestMapping methods.
For me works http://localhost:8080/versions/1 returns version 1.
And for any other request without version/{versionId} it returns index.
#Controller
public class DemoController {
#RequestMapping(value="/**/versions/{versionId}", method = RequestMethod.GET)
#ResponseBody
public String version(#PathVariable String versionId){
return "version " + versionId;
}
#RequestMapping(value="/**", method = RequestMethod.GET)
#ResponseBody
public String index(){
return "index";
}
}
If you want quite complicated request mapping try to overwrite handleRequest as it is here: How to define RequestMapping prioritization
Than you can:
if (urlPath.contains("/versions")) {
/* forward to method with #RequestMapping(value="/get/versions/{versionId}")
}
Instead of:
#RequestMapping(value="/**/versions/{versionId}", method = { RequestMethod.GET}, produces = "application/json")
use:
#RequestMapping(value="/response_for_versions/{versionId}", method = { RequestMethod.GET}, produces = "application/json")
Now all ".../versions/{versionId}" should be forwarded to "/response_for_versions/{versionId}" and all others would be handled by "/**"

Categories