Freemarker custom method - get ModelAndView without explicitely passing it - freemarker

I have written my own url routing mechanism, which allows mapping URLs to ModelAndViews and ModelAndViews back to URL (https://github.com/knyttl/Maite/wiki/Maite-Routing).
I am trying to create a Freemarker method which would modify the current ModelAndView and call the reverse routing process.
So in the template I would like to achieve something like:
${link("view", [id:10, page:1])}
Then the macro would be defined something like:
#Autowired
HandlerAdapter ha;
public TemplateModel exec(List args) throws TemplateModelException {
ModelAndView current = __getItSomehowFromTheTemplate();
if (current.getViewName() != (String) args.get(0)) {
// if the view is the same, we just modify the model
...
current.set...
} else {
// the view is different, we create a new ModelAndView
current = new ModelAndView();
}
// reverse routing process
return new SimpleScalar(ha.constructUrl(current));
}
I wonder whether I have to pass the ModelAndView to the method each time or I can let Freemarker somehow pass it automatically like with my magic method __getItSomehowFromTheTemplate();
If I have to pass it automatically, how can I do that? I did not find any direct reference to the current ModelAndView.
Thanks

How do you get the current ModelAndView in a template? From the data-model (template context) maybe? Because then you could call Environment.getCurrentEnvironment().getDataModel().get("theVariableName") to get it. (See: http://freemarker.org/docs/api/freemarker/core/Environment.html)

Related

RedirectView vs redirect: in spring mvc

which is better to use to redirect url in spring mvc:
return new ModelAndView(new RedirectView("../abc/list.vm"));
or
return new ModelAndView("redirect:DummyRedirectPage.htm");
The ModelAndView object holds an instance variable that references its view object and which may be either:
A concrete View implementation such as the case when you use:
new ModelAndView(new RedirectView("../abc/list.vm"))
A String object holding the symbolic name of the view prefixed by redirect: or forward:, such is the case when using:
new ModelAndView("redirect:DummyRedirectPage.htm")
Now when the Spring base web entry, DispatcherServlet, is called to render a View, it will try to resolve the requested view against the given ModelAndView object, either getting the reference to its View sub-implementation it its afforded or resolving the View and creating it out of the ModelAndView view string representation:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// ...
View view;
if (mv.isReference()) {
// Resolve the view and instantiate it...
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
// ...
}
// ...
}
Note that org.springframework.web.servlet.ModelAndView#isReference returns true if the view is a String object.
Given below details, I would assume that the first ModelAndView would involve less computation thus may be prefered to the later.

Spring MVC how to create controller without return (String) view?

The below is my sample controller.
#RequestMapping(value = "/validate", method = RequestMethod.POST)
public String validatePage1 (#ModelAttribute("page1")
Page1 pg1, BindingResult result) {
System.out.println("Value1:" + pg1.getVal1() +
"Value2:" + pg1.getVal2());
return "page2"; // I don't want to take any action (page navigation) here
}
#RequestMapping("/page1")
public ModelAndView pageShow() {
return new ModelAndView("page1", "command", new Page1());
}
Now the question is, I don't want to take any action in the client side when the method (validatePage1) is called by Spring framework, how to do?
Actually I have loaded all required pages in my client side at loading time (to avoid repeated page load), so I dont want to take any page navigation action in the client side, I just want to do the 'data binding' to complete my business logic in server side.
When I return "" empty string in "validatePage1()", Spring framework throws exception " Request processing failed; nested exception is org.apache.tiles.definition.NoSuchDefinitionException:" since I am using tiles, I have to remove tiles configuration later since I am loading all files at first loading itself.
You can set the method to return void and annotate it with #ResponseBody, as suggested here. No need to deal with HttpServletResponse etc.
Straight from the documentation:
Supported method return types
The following are the supported return types:
[...]
void if the method handles the response itself (by writing the response content directly, declaring an argument of type ServletResponse / HttpServletResponse for that purpose) or if the view name is supposed to be implicitly determined through a RequestToViewNameTranslator (not declaring a response argument in the handler method signature).

How to get the project name or path of from context or other class?

my project run on:
hXXp://localhost:8080/mvc
but when I rebuild on client part it would be:
hxxp://localhsot:8080/mvc-1.0.0-SNAPSHOT
I want get the parameter of mvc or mvc-1.0.0-SNAPSHOT in one of my controller of SpringMVC.
(because I will setting some dynamic picture path)
How to get these names in context or controller class?
Use HttpServletRequest.getContextPath():
#RequestMapping
public String handle(HttpServletRequest req) {
final String context = req.getContextPath(); //mvc or mvc-1.0.0-SNAPSHOT
//...
}

Intercepting the #responsebody in spring mvc

I have a Spring MVC web application with conroller like below :
#Controller
public class ActionRestController {
#RequestMapping(value = "/list", method = GET)
#ResponseBody
public List<Action> list(Action action, SearhCriteria searchCriteria) {
List<Action> ret = new ArrayList<Action>();
// Call a service method to get the records
// Copy the records into the list
// return the list of objects
return ret;
}
The Controller is invoked when the user does a search. There are several such controllers in the app, one for each searchable entity.
For reasons that I cannot explain very well, here, I cannot modify these controllers in anyway.
But now, I have requirement in the UI to display the search criteria and the no. of records and paging details, as well. This information is not returned by the controller. The JSON returned by the Controller contains just the list of records.
I have put up a different controller which will handle the request, gets and puts the extra info in the model and forwards the request to the existing controller like below :
#Controller
public class ActionExtendedController {
#RequestMapping(value = "/searchlist", method = GET)
#ResponseBody
public List<Action> list(Action action, SearhCriteria searchCriteria, Model model) {
model.addAttribute("searchParameters", searchCriteria);
return "forward:/list";
}
Upto this point, all is well.
What I want to do is intercept the request at a point where the List is returned from the controller, before it is converted to JSON, and return a map containing the list and the search parameters.
Now since the 'immutable' controller users ResponseBody the control goes to the JacksonMessageConverter amd the response goes out from there. I have already tried the following paths and they do not work.
Interceptor - By the time I get here, the response is already written out, so there is no way to change it.
Custom ObjectMapper for the JasksonMessageConverter - Will not work, since I do not have access to the model object inside the mapper, I only have access to the list returned by the controller.
Aspect #After pointcut for the controller - I think this technique will work, but I cannot get it to work. The advise does not fire and I am sure I am missing something in the configuration.
Is there a way to get Spring AOP to fire on a annotated controller, handler method or
can anyone suggest another method of intercepting the handler return value (along with the model) ?
How about a simple delegation to the base controller in your extended controller:
#Controller
public class ActionExtendedController {
#Autowired ActionRestController baseRestController;
#Autowired MappingJacksonJsonView mappingJacksonJsonView;
#RequestMapping(value = "/searchlist", method = GET)
public View list(Action action, SearhCriteria searchCriteria, Model model) {
List<Action> actions = baseRestController.list(action, searchCriteria, model);
model.addAttribute("actions", actions);
model.addAttribute("searchParameters", searchCriteria);
return mappingJacksonJsonView;
}
this way you are delegating to the original controller, but using this new controller for the view. Just register a mappingJacksonJsonView as a bean also which will serialize all model objects (searchcriteria and actions) into the json view. You need not even return a view but can also use #ResponseBody, with a type that can hold the responses and search criteria.
Why don't you change the return type to a Map? Like:
#Controller
public class ActionRestController {
#RequestMapping(value = "/list", method = GET)
#ResponseBody
public Map<String, Object> list(Action action, SearhCriteria searchCriteria) {
Map<String, Object> map = new HashMap<String, Object>();
List<Action> ret = new ArrayList<Action>();
// Call a service method to get the records
// Copy the records into the list
// return the list of objects
map.put("searchResult",ret);
map.put("searchCriteria", searchCriteria);
return map;
}

Duplicate RequestMapping

I'm working on an open source named OpenMRS supporting Spring MVC. I cannot modify core source for update purpose later. So I must write a module, something like plugin to add functions to the system. The problem is that I want to alter the original screen to mine by using portlet to redirect to my jsp. The controller of the core code is something like this:
#RequestMapping("/patientDashboard.form")
protected String renderDashboard(#RequestParam(required = true, value = "patientId") Integer patientId, ModelMap map){
....
return "patientDashboardForm";
}
I'm not familiar with Spring but as I know that when the url ends with /patientDashboard.form?patientId=xxx the function will call patientDashboardForm.jsp. Now I want to return to my jsp so I must define a new class with same code but return to my jsp (to do this because cannot modify the core code). But by defining same mapping /patientDashboard.form causes error "Cannot map handler XXX to URL path /patientDashboard.form: There is already handler YYY mapped".
So is there anyway to overcome this situation ?
There is no way to overrule an existing #RequestMapping. Each mapping must be unique. What you could do is the following. Instead of adding a request parameter, add a path parameter like this
#RequestMapping("/patientDashboard.form/{patientId}", method = RequestMethod.GET)
public String renderDashboard(#PathVariable("patientId") final long id, Model model) {
/* your code here */
}
This will create a new #RequestMapping that will differ from the existing one.
You have to create another #Controller extending the existing one. Then, you can define your custom mappings (you can't reuse the existing one) and reimplement the superclass methods at your convenience, redirecting to your view and defining custom logic there.
Example:
#Controller
#RequestMapping("/your_new_mapping")
public class YourController extends BaseController {
#Override
#RequestMapping("/patientDashboard.form")
public void renderDashboard(#RequestParam(required = true, value = "patientId") Integer patientId, ModelMap map){
// Call to default functionallity
super.renderDashboard(patientId, map);
...
// your custom code here
return "yourCustomJSPHere";
}
}

Resources