I have a controller that exposes the following endpoint:
#RequestMapping("/all-users")
List<User> getAllUsers() {
...
}
I have also an annotation that helps me out with versioning of those endpoints, which ends up on something like this:
#RequestMapping("/all-users")
#Version(version=1, latests=LATEST_ALL_USERS)
List<User> getAllUsers() {
...
}
Now I want to introduce an additional standard behavior to all handlers mapped wish method contains #Version annotation which will simply wrap the response object into another object which contains the current version and latest version of the invoked method. Some information to build this object are provided by #PathVariable parameters. I'm trying to find a hook that allows me that but no luck so far.
I tried first to have a custom RequestResponseBodyMethodProcessor but if I add it will not take any effect because the original RequestResponseBodyMethodProcessor comes before and I don't want to remove the ResponseBody from my endpoints.
Afterward I tried to go for the mapping instead, once I cannot handle it on the processor, maybe I could handle that on mapping time introducing my code pre and post method invocation, but got stuck on the point where mapping is registered where a method object is needed, not allowing me to introduce my advice code.
Is there any way to get this done?
Edit:
Some of the information needed to build the new returned object are provided as #PathVariables, and are available on end-point method call.
Related
I am collecting custom metrics for my controller endpoints via HandlerInterceptor. In postHandle I have everything I need and I would like to save the metrics for my endpoints along with the original route defined in the controller, so not the actual route filled with the data.
E.g. I have #GetMapping("/cats/{name}") and make GET request on /cats/tom I still want the defined route "cats/{name}"
I found that I can do this with the Object handler which is the parameter of postHandle -> I can cast it to HandlerMethod get the annotations of the method and find it from memberValues. This seems a bit overkill I also have to filter for Get-, Post-, Put-, Delete-, Patch-, RequestMapping annotations. Is there any easier or more straightforward way to do this? If so how? If not what else do I have to take into consideration with this solution?
It turns out Spring adds this data to the HttpServletRequest attributes. So it can be retrieved using:
String routePattern = (String) request.getAttribute("org.springframework.web.servlet.HandlerMapping.bestMatchingPattern");
I am creating a String based on some input parameters when a rest call is executed. I have created my own Annotation to intercept certain methods which will be called later on (within the same request). The Interceptor needs to know the String created at the beginning. My initial thought was to add the String into the Spring context like this:
ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory();
beanFactory.registerSingleton("myValue", "generatedString");
And just inject it to the interceptor. However i can only add it once this way and i really dont want it to be a singleton. Each request has its own "generatedString". I could not find a suitable way of solving this. What i actually want is to register an object on runtime for the current thread so i can inject it later on. When the Thread is done it should disappear.
Most of my experience with creating controllers with Spring are for REST controllers that consume JSON formatted requests. I've been searching for documentation on how to do testing for form submission, and so far this is how I understand it should go using MockMvc:
MvcResult result = mockMvc.perform(post("/submit")
.param('title', 'test title')
.param('description', 'test description'))
.andReturn()
However, I'm not sure how to map the form parameters to a model object. I've seen the #ModelAttribute annotation pop up in my searches but I can't figure out how it should be used for mapping. In addition, this quick start guide from the official documentation does not elaborate on how things like th:object and th:field translate to HTML and subsequently to URL encoded form.
I have my controller code similar to the following:
#PostMapping('/submit')
def submit(#ModelAttribute WriteUp writeUp) {
//do something with writeUp object
'result'
}
I discovered through trial and error that my specific problem might have been Groovy specific. There test code and the controller code, it turns out, have no issues. To reiterate, for testing form submission, use the param method through perform method of MockMvcRequestBuilders. Another thing to note is that this doesn't seem to work if content type is not specified. Here's a sample test code that works for me:
MvcResult result = webApp.perform(post("/submit")
.contentType(APPLICATION_FORM_URLENCODED) //from MediaType
.param('title', 'test title')
.param('description', 'test description'))
.andReturn()
As you can see, it's not much different from what I posted originally. The controller code is pretty much just the same, with #ModelAttribute working just fine.
The problem with my setup though was that since I was using Groovy, I assumed that getters and setters were automatically generated in my WriteUp class. Here's how the WriteUp class looked originally:
class WriteUp {
private String title
private String description
}
I haven't written code in Groovy for a while, and the last time I did, classes like the one above can be assumed to have getters and setters implicitly. However, it turns out that is not the case. To solve my specific issue, I updated the access modifier in the fields to be default (package level):
class WriteUp {
String title
String description
}
I've seen the #ModelAttribute annotation pop up in my searches but I
can't figure out how it should be used for mapping.
When you mark your writeUp object with #ModelAttribute, then the Spring container populates the parameters (like title, description, etc..) from HttpServletRequest object & injects the object to the controller method, when the request comes to the server from the client (could be a Browser or MockMvc unit test client or anything else).
Also, few other basic points for your quick understanding:
(1) Controller methods are mapped to an URI and RequestMethod (like POST/GET/DELETE/PUT et..) like shown below:
#RequestMapping(value="/submit", method=RequestMethod.POST)
public String submit(#ModelAttribute WriteUp writeUp) {
//Call the service and Save the details
model.addAttribute("Writeup details added successfully");
return "writeUpResult"; //Returns to the View (JSP)
}
(2) #ModelAttribute will be mapped to an object (like your writeUp) for http POST/PUT requests where the html formd data is part of http body.
(3) #RequestParam or #PathParam will be used for http GET requests where the parameters are part of URL (i.e., not part of http body).
You can look here for understanding the DispatcherServlet request handling & Spring MVC basic web flow.
I am wondering if there is a way to wrap all argument resolvers like for #PathVariables or #ModelAttributes into one single transaction? We are already using the OEMIV filter but spring/hibernate is spawning too many transactions (one per select if they are not wrapped within a service class which is be the case in pathvariable resolvers for example).
While the system is still pretty fast I think this is not necessary and neither consistent with the rest of the architecture.
Let me explain:
Let's assume that I have a request mapping including two entities and the conversion is based on a StringToEntityConverter
The actual URL would be like this if we support GET: http://localhost/app/link/User_231/Item_324
#RequestMapping("/link/{user}/{item}", method="POST")
public String linkUserAndItem(#PathVariable("user") User user, #PathVariable("item") Item item) {
userService.addItem(user, item);
return "linked";
}
#Converter
// simplified
public Object convert(String classAndId) {
return entityManager.find(getClass(classAndId), getId(classAndId));
}
The UserService.addItem() method is transactional so there is no issue here.
BUT:
The entity converter is resolving the User and the Item against the database before the call to the Controller, thus creating two selects, each running in it's own transaction. Then we have #ModelAttribute methods which might also issue some selects again and each will spawn a transaction.
And this is what I would like to change. I would like to create ONE readonly Transaction
I was not able to find any way to intercept/listen/etc... by the means of Spring.
First I wanted to override the RequestMappingHandlerAdapter but the resolver calls are well "hidden" inside the invokeHandleMethod method...
The ModelFactory is not a spring bean, so i cannot write an interceptor either.
So currently I only see a way by completely replacing the RequestMappingHandlerAdapter, but I would really like to avoid that.
And ideas?
This seems like a design failure to me. OEMIV is usually a sign that you're doing it wrong™.
Instead, do:
#RequestMapping("/link/User_{userId}/Item_{itemId}", method="POST")
public String linkUserAndItem(#PathVariable("userId") Long userId,
#PathVariable("itemId") Long itemId) {
userService.addItem(userId, itemId);
return "linked";
}
Where your service layer takes care of fetching and manipulating the entities. This logic doesn't belong in the controller.
I need to the ignore the serialization (JSon/Jackson and XML/XStream) of some attributes from my object response based on user input/or Spring security roles (like you don't have permission to see the content of this field, but all others are ok etc). How is the best approach to do this in Spring MVC Rest?
Other approach is show only the attributes that are relevant for the api user, like described here http://googlecode.blogspot.com.br/2010/03/making-apis-faster-introducing-partial.html
If you are using Jackson, here are some possible options to modify the Json serialization:
Use the #JsonIgnore annotation - However, this is static filtering, and will not allow you to implement rule-based filtering as you appear to need
Use #JsonFilter - This will allow you to implement an interface in which you can provide your serialization filtering logic. You may find this to be too heavyweight of a solution.
The way I often solve this is to return a Map from my Controller methods instead of the underlying object. You can write processing code that puts the relevant fields from the object into the Map, therefore giving you complete control over what is serialized. You could include a method on the Object to do the conversion. The method could look something like this:
// RequestObj is whatever 'input' object that indicates what should be filtered
public Map<String,Object> convertToMapForRequest(RequestObj request){
// Build return map based on 'this' and request
}