Since I'm using the CQRS pattern, I'm trying to create a single controller method that accepts every POST call with a command in its request body and send it.
I'm almost there, but I can't get the path variables.
I created a custom HandlerMapping
#Bean
public HandlerMapping requestMappingHandlerMapping() throws NoSuchMethodException {
for (final UrlEnum urlEnumItem : UrlEnum.values()) {
requestMappingHandlerMapping.registerMapping(new RequestMappingInfo(urlEnumItem.getCommandName(),
new PatternsRequestCondition(urlEnumItem.getUrl()),
null,
null,
null,
null,
null,
null),
commandController,
commandController.getClass().getDeclaredMethod("commandHandler", HttpServletRequest.class)
);
}
return requestMappingHandlerMapping;
}
and this is my controller method signature
#RequestMapping(method = {RequestMethod.POST, RequestMethod.PUT}, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Object> commandHandler(final HttpServletRequest request) throws Exception {
// controller code here
}
If the url path is something like /api/test it works, but with something like /api/test/{idEntity} I don't have any PathVariable available in the request.
I tried everything like
String originalUrl = (String) request.getAttribute(
HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
which returns the valued url (i.e. /api/test/1234), not the template, or adding
#PathVariable Map<String, Object> parameters
as a parameter in the method, which is empty.
Debugging the request object it seems there isn't anything useful to identify the path variables.
Maybe I should interrogate the HandlerMapping, but I can't have access to it in the controller method.
Is there a way to extract the pathVariables in the controller method?
It was an error in the configuration. I shouldn't have added the RequestMapping annotation to the controller method because it overrode my configuration.
Now I have
#RestController
public class CommandController extends AbstractController {
private final MappingJackson2JsonView mappingJackson2JsonView = new MappingJackson2JsonView();
#Override
protected ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
// controller code here
return new ModelAndView(mappingJackson2JsonView);
}
}
Related
I have a custom dispatcher servlet which extends the default DispatcherServlet to do some validation for all requests.
Since I will get all the parameters from a request(getInputStream()->Map) to do some validation, I want to pass the params to controller or add the params to the context where I can get them again from the cotroller.
Now I just put all the params to a global Map, but I wonder if there are some simple ways.
public class CustomDispatcherServlet extends DispatcherServlet {
private static final long serialVersionUID = 7250693017796274410L;
#Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
doFilter(request, response);
super.doDispatch(request, response);
}
...
private void doFilter(HttpServletRequest request, HttpServletResponse response) {
WLNResponse<String> error = null;
try {
boolean isSignValid = checkSignValidity(request);
...
private boolean checkSignValidity(HttpServletRequest request) throws IOException {
// pass this params to controller or somewhere I can get from controller
Map<String, Object> params = WebUtils.readParams(request);
...
The way I would go at validating params in the controller itself. for instance
#Controller
public ControllerClass
{
#RequestMapping(value = "/someurl", method = RequestMethod.GET, params = {
"requestParam"})
public void someMethod(#RequestParam(value = "requestParam") String requestParam)
{
System.out.println("This is the value of the RequestParam requestParam " + requestParam);
}
}
This way you can do your validation within the controller.
The only thing this doesn't solve for is if the request being made is not resolved to a valid controller. For that I would use the annotation #controllerAdvice.
Currently, I simply use the request.setAttribute() to put params to the attributes and get it from the controller.....
What is the most elegant/effective way, how to handle Model between Controllers in Spring MVC 3.2. For redirecting to another Controller I use forward method, so there is not necessary new instance of request and Model data should be accessible (if I am not wrong). Is there any way how to catch Model, which was added in first Controller?
(I know about RedirectAttributes, but may be is better/easier method)
Example:
#Controller
public class WebpageController{
#RequestMapping( value = { "/{code}" } )
public String handleFirstLevel(#PathVariable String code, ModelMap modelMap) throws PageNotFoundEception{
final Webpage webpage = getWebpage(code);
modelMap.put(WEBPAGE_MODEL_KEY, prepareModel(webpage));
return "forward:some-url";
}
private Map<String, Object> prepareModel(Webpage webpage){
Map<String, Object> model = new HashMap<String, Object>();
model.put("webpage", webpage);
return model;
}
// some other code
}
#Controller
public class SpecialWebpageController{
#RequestMapping( value = { "/some-url" } )
public String handleFirstLevel(#PathVariable String code, ModelMap modelMap) throws PageNotFoundEception{
// need access to previously appended model to add some other data
return "specialViewName";
}
}
Thank you
When you have a handler method that simply returns a String, that String is considered a view name. With a prefix of forward, Spring will get a RequestDispatcher for the specified path and forward to it. Part of that process will include taking the Model from the ModelAndView created for that request handling cycle and putting all its attributes into the HttpServletRequest attributes.
The Servlet container will take the RequestDispatcher#forward(..) and again use your DispatcherServlet to handle it. Your DispatcherServlet will create a new ModelAndView with a new Model for this handling cycle. Therefore this Model doesn't contain any of the attributes from before but the HttpServletRequest attributes do.
In your case, this
modelMap.put(WEBPAGE_MODEL_KEY, prepareModel(webpage));
will end up being in
HttpServletRequest request = ...;
request.getAttribute(WEBPAGE_MODEL_KEY);
I'm new to Jersey, and want to determine the #Produces type in other contexts, so I can use it during error handling cases.
For example, I have the following method that produces json:
#Path("test-json")
#Produces(MediaType.APPLICATION_JSON)
#GET
public Object getTestJson(#Context HttpServletRequest req, #Context HttpServletResponse res) throws Exception
{
throw new RuntimeException("POST submitted without CSRF token! ");
}
Later on, in a global exception handler, I'd like to get the #Produces media type.
I've tried doing this with something like the following, but getMediaType() is returning null (note that this is simplified, but headers is not null in all of my tests, just getMediaType() is null).
public class someClass
{
#Context
HttpHeaders headers;
public Response convertExceptionToResponse(T exception)
{
MediaType mediaType = headers.getMediaType();
// At this point, I thought media type would be
// MediaType.APPLICATION_JSON
// for the above 'getTestJson' method, but it's null.
}
}
How can I do this?
JAX-RS
Inject ResourceInfo and invoke getResourceMethod() which will return Java Method. Then you can simple retrieve declared annotations. The problem here is that with this approach you need to do a lot of coding in case #Produces is not located directly on a method but somewhere in the hierarchy.
Jersey 2
Inject ExtendedUriInfo
#Context
private ExtendedUriInfo uriInfo;
and look for matched ResourceMethod (getMatchedResourceMethod()). Then simply get list of producible media types (getProducedTypes()).
I have a controller like this:
#Controller
public class HomeController {
#RequestMapping(value = "/update", method = RequestMethod.POST)
public String update(#RequestParam("user") User user, ModelMap model){
SaveUserToDatabase(user);
return "index";
}
#ModelAttribute("user")
String getUser() {
return LoadCurrentUserFromDataBase();
}
}
In short, my views would render user in almost every actions in HomeController,
but I don't want to code:
model.addAttribute("user", LoadCurrentUserFromDataBase())
in every actions, instead I'm seeking a way like #ModelAttribute to expose user to all my views.
However, according to the docs, #ModelAttribute methods in a controller are invoked before #RequestMapping methods, within the same controller.
As to my code, getUser is called before update, but i'd like to get the updated user.
Is there a way to expose the user attribute after actions without explicitly call model.addAttribute in every actions?
Each time you do a POST, make a redirection. That way, your POST method will only be responsible for updating the data, and the updated data will be loaded by the target controller.
So in this case, the update() method would redirect to another controller which would call the getUser() method before its GET method.
The Post / redirect / GET solution is valid if it works for you.
However, I had a similar situation where I had model attributes that needed to be written by all my controllers, after request processing (tracking information mostly). What I did was to register a custom interface (e.g. AdditionalModelDataSupplier), which I apply to all controllers that need to provide additional data. The interface would have a method like this:
void provideAdditionalData(Model model, HttpServletRequest request);
Now, I wrote an interceptor that in the postHandle method checks the Controller bean for this interface and calls this method:
#Override
public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler,
final ModelAndView modelAndView) throws Exception {
AdditionalModelDataSupplier modelDataSupplier = findAdditionalModelDataSupplier(handler);
if (modelDataSupplier != null) {
final ModelMap modelMap = modelAndView.getModelMap();
final Model targetModel;
if (modelMap instanceof Model) {
targetModel = (Model) modelMap;
} else {
// the modelmap doesn't implement model, so we need to provide a wrapper view
targetModel = new ForwardingModel(modelMap);
}
modelDataSupplier.provideAdditionalData(targetModel, request);
}
}
#Nullable
private static AdditionalModelDataSupplier findAdditionalModelDataSupplier(final Object handler) {
if (handler instanceof AdditionalModelDataSupplier) {
return (AdditionalModelDataSupplier) handler;
}
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Object bean = handlerMethod.getBean();
if (bean instanceof AdditionalModelDataSupplier) {
return (AdditionalModelDataSupplier) bean;
}
}
return null;
}
(the ForwardingModel class mentioned above is trivial to create, it just delegates everything to the ModelMap)
I would like to know how to read a flash attributes after redirection in Spring MVC 3.1.
I have the following code:
#Controller
#RequestMapping("/foo")
public class FooController {
#RequestMapping(value = "/bar", method = RequestMethod.GET)
public ModelAndView handleGet(...) {
// I want to see my flash attributes here!
}
#RequestMapping(value = "/bar", method = RequestMethod.POST)
public ModelAndView handlePost(RedirectAttributes redirectAttrs) {
redirectAttrs.addFlashAttributes("some", "thing");
return new ModelAndView().setViewName("redirect:/foo/bar");
}
}
What I am missing?
Use Model, it should have flash attributes prepopulated:
#RequestMapping(value = "/bar", method = RequestMethod.GET)
public ModelAndView handleGet(Model model) {
String some = (String) model.asMap().get("some");
// do the job
}
or, alternatively, you can use RequestContextUtils#getInputFlashMap:
#RequestMapping(value = "/bar", method = RequestMethod.GET)
public ModelAndView handleGet(HttpServletRequest request) {
Map<String, ?> inputFlashMap = RequestContextUtils.getInputFlashMap(request);
if (inputFlashMap != null) {
String some = (String) inputFlashMap.get("some");
// do the job
}
}
P.S. You can do return return new ModelAndView("redirect:/foo/bar"); in handlePost.
EDIT:
JavaDoc says:
A RedirectAttributes model is empty when the method is called and is
never used unless the method returns a redirect view name or a
RedirectView.
It doesn't mention ModelAndView, so maybe change handlePost to return "redirect:/foo/bar" string or RedirectView:
#RequestMapping(value = "/bar", method = RequestMethod.POST)
public RedirectView handlePost(RedirectAttributes redirectAttrs) {
redirectAttrs.addFlashAttributes("some", "thing");
return new RedirectView("/foo/bar", true);
}
I use RedirectAttributes in my code with RedirectView and model.asMap() method and it works OK.
Try this:
#Controller
public class FooController
{
#RequestMapping(value = "/foo")
public String handleFoo(RedirectAttributes redirectAttrs)
{
redirectAttrs.addFlashAttribute("some", "thing");
return "redirect:/bar";
}
#RequestMapping(value = "/bar")
public void handleBar(#ModelAttribute("some") String some)
{
System.out.println("some=" + some);
}
}
works in Spring MVC 3.2.2
For all those like me who were having problems with seeing the POST url in the browser when a validation would fail.
The POST url is a private url that should not be exposed to users but it was automatically rendered when a validation failed. i.e. if a field was below a minimum length. I was using #Valid. I wanted the original GET url of the form to show at all times even when validation bounced back to the form, so I did the following:
if (validation.hasErrors()) {
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.story", validation);
redirectAttributes.addFlashAttribute("story", story);
return new ModelAndView("redirect:/january/2015");
where story is the form object representation, redirectAttributes are RedirectAttributes you put in the method signature and validation is the BindingResult. /january/2015 is the mapping to the GET controller where the form lives.
After this implementation, in the mapping for /january/2015, story comes in intact as follows:
Story story= (Story) model.asMap().get("story");
//story from the POST method
I had to augment my GET method and check if this was not null. If not null, then send this to the form else I would send a newly initialized Story type to the form as default behaviour before.
In this manner, I am able to return to the form with the bindingresults intact (errors show on form) but have my GET url in place of the post url.