Spring Data Rest Custom Method return String - spring

I need a custom Method on Spring Data Rest that has some kind of input and returns a String.
#BasePathAwareController
#RequestMapping(value = "/businessActions")
public class BusinessActionController implements ResourceProcessor<RepositoryLinksResource> {
/**
* This BusinessAction's purpose is: Generiert für ein Modell das entsprechende Barrakuda-File.
* It returns one String.
*/
#RequestMapping(value = "/modellGenerieren", method = RequestMethod.GET)
public String modellGenerieren(#Param(value="project") String project) throws IOException {
// Get project by id from repository and map to string.
return "asdf\n";
}
}
By using #BasePathAwareController the endpoint will return "asdf\n", my desired output would be:
asdf
<new line>
Im able to produce this output by using only #Controller, but this breaks the awareness of the base path and i need the PersistentEntityResourceAssembler in other methods of this Controller - the assembler cannot be injected then.

The Bottom Line
It can be solved by using the following mapping and configuration:
// The OP's original controller with a small tweak
#BasePathAwareController
#RequestMapping("/businessActions")
public class MyCustomRestEndpoint {
// Let's specify the #produces type as text/plain (rather than the Spring Data REST JSON default)
#GetMapping(value = "/modellGenerieren", produces = MediaType.TEXT_PLAIN_VALUE)
public #ResponseBody ResponseEntity<String> modellGenerieren(#Param(value="project") String project) throws IOException {
// Get project by id from repository and map to string.
return ResponseEntity.ok("A String!");
}
}
#Configuration
public class PlainTextConfiguration implements RepositoryRestConfigurer {
// Allow for plain String responses from Spring via the `text/plain` content type
#Override
public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters)
{
StringHttpMessageConverter converter = new StringHttpMessageConverter();
converter.setSupportedMediaTypes(configureMediaTypes());
messageConverters.add(converter);
}
private List<MediaType> configureMediaTypes() {
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.TEXT_PLAIN);
mediaTypes.add(MediaType.parseMediaType("text/plain;charset=iso-8859-1"));
mediaTypes.add(MediaType.parseMediaType("text/plain;charset=UTF-8"));
mediaTypes.add(MediaType.parseMediaType("text/plain;charset=UTF-16"));
return mediaTypes;
}
}
And by specifying the ACCEPT header when making the request (this is the key!):
GET http://localhost:8080/api/businessActions/modellGenerieren
Content-Type: text/plain
Accept: text/plain
This yields the following response:
GET http://localhost:8080/api/businessActions/modellGenerieren
HTTP/1.1 200 OK
Date: Mon, 24 Dec 2018 06:21:10 GMT
Content-Type: text/plain;charset=iso-8859-1
Accept-Charset: ... <large charset>
Content-Length: 9
A String!
Response code: 200 (OK); Time: 151ms; Content length: 9 bytes
The Reason
Upon investigation, it appears that the reason you can never seem to return the unquoted String is due to the behavior of the BasePathAwareHandlerMapping#lookupHandlerMethod function.
lookupHandlerMethod basically assumes that when making a request on a method, that the permissible media types are made with the HTTP request in the ACCEPT header. Otherwise it defaults to the default media type (configurable using RepositoryRestConfigurer#configureRepositoryRestConfiguration).
The default value for the default media type for Spring Data REST is either application/json or application/hal+json (depending on that default value, see here). That's why you are ONLY seeing application/json content types with the double quote, "", around your strings in the result. The String is being converted using the Jackson converter (which encloses Strings with quotes) and not a String converter.
After looking into it, I agree with you that this seems like a strange assumption. That is, the framework shouldn't assume that all requests are always explicitly specify the ACCEPT header with the desired media type (at least, I personally don't always expect to see it) and otherwise assume that all requests should be of the default media type only specifically because of a use case like yours.
Without looking too deeply into the documetation, the fact that #BasePathAwareController seems to imply that more than just the standard Spring Data Rest entities are fair game to use when leveraging Spring Data REST.
I'd personally return the produces type to the client even if the ACCEPT header wasn't specified -- and if I were to write some code to modify the BasePathAwareHandlerMapping, I'd add the following regarding my commented line:
#Override
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
...
if (!defaultFound) {
mediaTypes.add(configuration.getDefaultMediaType());
}
// Lookup the Handler Method for this request
// If no media types are specific in the ACCEPT header of this request...
// Then look and see if the method has a #produces specified and define that as the ACCEPT type
super.lookupHandlerMethod(lookupPath, new CustomAcceptHeaderHttpServletRequest(request, mediaTypes));
}

I have the same problem. And I have not solved it completely. But I hope this additional info at least helps:
There are 4 annotations in Spring and spring-data-rest that all are IMHO chaotically intermingeled. (See for example this Bug)
Spring Data Rest controllers: behaviour and usage of #BasePathAwareController, #RepositoryRestController, #Controller and #RestController
If you use #BasePathAwareController you get all the magic from Spring-data-rest (see SDR Doc) => But then you CANNOT return a simple String. (At least I did not found a way so far.)
If you use #RestController then your endpoint is completely indipendent from SDR.
If you want a #RestController to be exposed unser the same path prefix as the rest of your SDR API then you can use this:
#RestController
#RequestMapping("${spring.data.rest.base-path}")
public class MyCustomRestEndpoint { ... }
This reads the path prefix from application.properties
spring.data.rest.base-path=/api/v17
Then you can return a plain String.
But if you use #BasePathAwareController, as the OP said, because you need PersistentEntityResourceAssembler, then there is no way of returning a plain String.

Related

How can i explicitly check request content-type is matching with actual content in Spring boot?

I want to validate my request content-type is matching with my request body or not?
I am trying to implement the OWASP Rest security standard.
Which says :
13.2.5 Verify that REST services explicitly check the incoming Content-Type to be the expected one, such as application/XML or application/JSON.
In the below image the content type is JSON but the request is in XML.Still it's working fine.
My Controller code:-
#RestController
public class TestController {
#PostMapping(path="/hello",consumes = "application/json")
public Map<String,String> hello(Master ecm){
Map<String,String> m=new HashMap<>() ;
m.put("message", "hello!");
return m;
}
}
Spring annotations provide a specific attribute for that. It is called consumes.
Here is an example
#PostMapping(consumes = MediaType.SELECT_TYPE_HERE)
specific example
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)

5 levels of media type Spring REST

I am trying to apply CQRS principles on my REST API with domain-driven-design principles, using the 5 levels of Media Types, as explained in these articles:
https://www.infoq.com/articles/rest-api-on-cqrs
http://byterot.blogspot.ch/2012/12/5-levels-of-media-type-rest-csds.html
My technical context is Spring REST framework version 3.2.
Basically, i need to be able to map my commands using different "domain-model" media types.
Therefore, i would expect the following mapping to work:
#Controller
#RequestMapping("resources")
public class MyController {
#RequestMapping(value = "{id}", method = RequestMethod.PUT, consumes = "application/json;domain-model=CommandOne")
#ResponseBody
public void commandOne(#PathVariable Long id, #RequestBody CommandOne commandOne) {
LOG.info("Using command {}", commandOne);
}
#RequestMapping(value = "{id}", method = RequestMethod.PUT, consumes = "application/json;domain-model=CommandTwo")
#ResponseBody
public void commandTwo(#PathVariable Long id, #RequestBody CommandTwo commandTwo) {
LOG.info("Using command {}", commandTwo);
}
}
Problem is, I am getting mapping errors when requesting for a PUT:
PUT /resources/123
Content-Type: application/json;domain-model=CommandOne
Error is:
java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path ...
Spring doesn't allow me to map the same uri the different domain-model Media Types. Any idea how could I achieve that? Am I missing something?
Many thanks
:o)
That's because the content-type is still the same application/json. Please look at Content-Type syntax
What you are passing as domain-model=CommandOne is just a parameter and Spring doesn't recognize as a difference to call the different methods.
This is described in more detail on answer
Does HTTP content negotiation respect media type parameters
This was submitted as a BUG to the Spring team but they closed with "Work as designed".
Unfortunately Spring can't treat this case currently.

Parsing JSON request body with Spring MVC

I am using Spring 4.1 framework for developing webservices. When I return a Java object as response, it is automatically converted to JSON and delivered to client, so I assume that JSON parser is in classpath and it is configured properly. However it fails to convert the request body from JSON into Java object and client is getting a HTTP response of 400.
Here is how the webservice looks like:
public class Details{
public Details(){
}
int code;
int area;
}
#RequestMapping(value = "/api/update/{phoneNumber}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> update(#PathVariable final String phoneNumber, #RequestBody Details details)
Here is how the request looks like:
Method: Post
Content-Type: application/json; charset=utf-8
Body: {"code":0,"area":12}
If I collect the request body as string and parse it manually then it works, so it gets the valid JSON but for some reason it is not parsing it automatically. I have no clue on how to fix it. Please help. Thanks in advance.
You have package-private properties in your Details class, so they are probably not recognised by json-converter.
You have several options:
define them as public (not recommended)
provide getters and setters
if you are using jackson, you can annotate them with #JsonProperty, leaving them package-private
Finally I got the reason for this. I was using inner classes which were not static. Making those static fixed the issue.

Spring MVC: How to treat html suffix like any RequestMapping

I have a legacy system and some url rewrite rule that we want to get rid. One of which is a rule to change is /tools/lookup.html?what=this and change it to /tools/search?what=this and actually returns json and not html !
I'm trying to find a way to have a #Controller to support the legacy format lookup.html, but it fails with HTTP 406 "The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers.". I'm wondering if anyone as done something similar?
My controller methods looks like:
#RequestMapping(value = "/tools/lookup.html", method = RequestMethod.GET)
public #ResponseBody Result lookup() {
return result;
}
Thanks in advance
Sylvain
Removing the reponsebody annotation will stop the controlle method returning json.
Take a closer look at #RequestMapping, which supports a produces element. E.g.,
#RequestMapping(value = "/tools/search", produces = "application/json")
#ResponseBody
public Result search(...) { ... }
The issue comes from how spring is treating pathvariables. The default behavior will cut off the last dot in a url (.html) to find the request mappings.
This effect happens only for the last pathvariable.
I havent found a property yet to change this globally but one way is to tell your pathvariable mapping to use the regex {pathvariable:.+}.
#Requestmapping("/somepath/{varwithextention:.+}")
public String method(#Pathvariable String varwithextension) {
...
}
Edit: i see that you do not even use pathvars. Probably its still the same effect for the last url part though?
Hi finally found something that should work in my case, note that my app doesn't need to support real html (REST only app), so this shouldn't have to much side effects. In my WebMvcConfigurerAdapter I added the following media type for html.
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("html",MediaType.APPLICATION_JSON);
configurer.mediaType("html",MediaType.APPLICATION_XML);
super.configureContentNegotiation(configurer);
}
I now I get my JSON or XML content back in the client. No more 406 error.

Alternative of #RequestBody

Is there an alternative way to obtain a reference to a request's body without using the approach with the annotation? I'm using GAE + Spring and everytime I use #RequestBody in the signatures of my controller methods, the server returns 415 Unsupported Media Type. All I'm trying to do is read a JSON encoded message on a Post method. Thanks.
You can take in a parameter of HttpServletRequest, and read the ServletInputStream using getInputStream() from there. The stream will contain the body of the request.
#Controller
public class MyController {
#RequestMapping("/test")
public String aTest(HttpServletRequest request) {
InputStream body = request.getInputStream();
//process request body
return myReturnVal;
}
}
Try this, on the RequestMapping configure the headers to accept application/json and be sure to configure a jackson message convertor for this type

Resources