Spring: Controller #RequestMapping to jsp - java

Using Spring 3.1.2
How do you reference the Annotated value of the CONTROLLER (not method) RequestMapping from a jsp so I can build URL's relative to the Controller. If the method level request mappings are referenced, it will cause my links to not work, so they cannot be part of the this in any way.
For example (Note that this controller's mapping is "/foo/test"):
#Controller
#RequestMapping("/foo/test")
public class FooController {
#RequestMapping(value="/this", method=RequestMethod.GET)
public String getThis() {
return "test/this";
}
#RequestMapping(value="/that", method=RequestMethod.GET)
public String getThat() {
return "test/that";
}
#RequestMapping(value="/other", method=RequestMethod.GET)
public String getOther() {
return "test/other";
}
}
...and from my this.jsp:
<%# taglib uri="http://www.springframework.org/tags" prefix="s"%>
<s:url value="${controllerRequestMapping}" var="baseUrl"/>
That
Other
The reason I need to access the RequestMapping is because my views may be accessed from multiple Controllers. Note that this controller's mapping is "/bar/test"!
#Controller
#RequestMapping("/bar/test")
public class BarController {
#RequestMapping(value="/this", method=RequestMethod.GET)
public String getThis() {
return "test/this";
}
#RequestMapping(value="/that", method=RequestMethod.GET)
public String getThat() {
return "test/that";
}
#RequestMapping(value="/other", method=RequestMethod.GET)
public String getOther() {
return "test/other";
}
}
Some of my Controller level request mappings have path variables too, so getting just the string value of the request mapping will not work. It will have to be resolved:
#Controller
#RequestMapping("/something/{anything}/test/")
public class SomethingController {
...
}
Update
Maybe if there was a way to modify the context path by appending the Controller request mapping to it BEFORE the Spring URL tag, that would solve the problem.
contextPath = contextPath/controllerRequestMapping
Then, I could do something like this because I believe Spring will automatically retrieve the current context path:
<%# taglib uri="http://www.springframework.org/tags" prefix="s"%>
That
Other
This, of course, would be optimal! Ideas? Thoughts...?
Thanks in advance!

You could get the URI using the Servlet API. There is no "Spring" way to do this as far as I know.
#RequestMapping(value="/this", method=RequestMethod.GET)
public String getThis(HttpServletRequest request, Model m) {
m.addAttribute("path", request.getRequestURI());
return "test/this";
}
Then in your JSP:
This
For more information about HttpServletRequest, see the API here.

There is no built-in way to access the #RequestMapping, but you can simply add the URL to the model and reference it from the view that way.

According to me there is no difference in putting #RequestMapping annotation at both class level and method level. Instead of that you can have the combined #RequestMapping annotation on top of the method only.
So now your method level annotation will be something like this.
#RequestMapping(value="("/foo/test/this", method=RequestMethod.GET)
or
#RequestMapping(value="("/bar/test/this", method=RequestMethod.GET)
Now since both your methods return the same view you can have just one method for both of them. And in the annotation mapping value instead of foo and bar you can inroduce one path variable {from}. So finally your annotation and the method will be like this.
#RequestMapping(value="("/{from}/test/this", method=RequestMethod.GET)
public String getThis(#PathVariable("from") String from) {
return "test/this";
}
And after doing this in your method you can perform different calculations or operations on based of the runtime value of path variable from you get. And in the JSP page you can simple get this value with ${from}.
Hope this helps you. Cheers.

See code below to access request mapping, if you are using Spring 3.1 and above. I'm not sure if this is what you require. This is just an attempt and of course i do not know of any straight forward way. Invoke getRequestMappings() from inside a method that has request mapping.
public class FooController{
private RequestMappingHandlerMapping handlerMapping = null;
#Autowired
public FooController(RequestMappingHandlerMapping handlerMapping){
this.handlerMapping = handlerMapping;
}
public Set<String> getRequestMappings(){
Map<RequestMappingInfo, HandlerMethod> handlerMap = handlerMapping.getHandlerMethods();
Iterator<RequestMappingInfo> itr = handlerMap.keySet().iterator();
while(itr.hasNext()){
RequestMappingInfo info = itr.next();
PatternsRequestCondition condition = info.getPatternsCondition();
Set<String> paths = condition.getPatterns();
HandlerMethod method = handlerMap.get(info);
if(method.getMethod().getName().equals(Thread.currentThread().getStackTrace()[2].getMethodName()))
return paths;
}
return new HashSet<String>();
}
}

As of 4.1 every #RequestMapping is assigned a default name based on the capital
letters of the class and the full method name. For example, the method getFoo
in class FooController is assigned the name "FC#getFoo". This naming strategy isenter code here
pluggable by implementing HandlerMethodMappingNamingStrategy and configuring it on your
RequestMappingHandlerMapping. Furthermore the #RequestMapping annotation includes a
name attribute that can be used to override the default strategy.
The Spring JSP tag library provides a function called mvcUrl that can be used to prepare links to
controller methods based on this mechanism.
For example given:
#RequestMapping("/people/{id}/addresses")
public class MyController {
#RequestMapping("/{country}")
public HttpEntity getAddress(#PathVariable String country) { ... }
}
The following JSP code can prepare a link:
<%# taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
Get Address
Make sure you have configured your MVC using either Java Config or XML way and not both the ways.
You will get an exception as :more than one RequestMappingInfo HandlerMapping found.

Related

Is it possible to use two controller classes with one URI in Java Spring?

I m working on this project where am I trying to access two different controllers with the same URI. After trying to run it I m getting a BeanCreationException.
So it happens that I m getting an Error while creating a bean.
I hope there is a way to deal with this.
The error message that I m getting:
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'requestMappingHandlerMapping' defined in
class path resource
[org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]:
Invocation of init method failed; nested exception is
java.lang.IllegalStateException: Ambiguous mapping. Cannot map
'userController' method public java.lang.String
com.javalanguagezone.interviewtwitter.controller.UserController.overview(java.security.Principal,org.springframework.ui.Model)
to {[/overview],methods=[GET]}: There is already 'tweetController'
bean method
I m using as well Thymleaf for this project. The URI that I m for those two controllers: http://localhost:8080/api/overview.The two controllers are providing my Thymleaf page with information that i have to present at the same time with the URI just mentioned. With this, I m calling both controllers but I m getting a previously mentioned error.
The first controller class(TweetController):
#Controller
#Slf4j
public class TweetController {
private TweetService tweetService;
public TweetController(TweetService tweetService) {
this.tweetService = tweetService;
}
#GetMapping( "/overview")
public String tweetsFromUser(Principal principal, Model model) {
model.addAttribute("tweets",tweetService.tweetsFromUser(principal).size());
return "api/index";
}
}
The second controller class is:
#Controller
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
#GetMapping("/followers")
public String followers(Principal principal) {
userService.getUsersFollowers(principal);
return "api/index";
}
#GetMapping("/following")
public int following(Principal principal) {
return userService.getUsersFollowing(principal);
}
#GetMapping("/overview")
public String overview(Principal principal, Model model){
model.addAttribute("followers",userService.getUsersFollowers(principal));
model.addAttribute("following",userService.getUsersFollowing(principal));
return "api/index";
} }
My question: is there a way a fix it or I to look for another way around? I m relatively a newbie with Spring. Thank you for your help in advanced.
according to REST conventions, you should not have /overview, but /user/overview. You can set it by supplying #RequestMapping("/user") in your userController.
In the same way you would have "/tweet/overview" endpoint.
#Controller
#Slf4j
#RequestMapping("/tweet")
public class TweetController {
doing it any other way is against conventions, against Spring Rules and probably means you're doing something wrong. Spring does not allow two methods with same uri because it does not know which method exactly you would want to call.
upd: if you need logic, you can send parameters to GET: /overview?customParam=user
#GetMapping( "/overview")
public String tweetsFromUser(#RequestParam(value="customParam") String
param, Principal principal, Model model) {
// logic checking customParam...
But that CANNOT be in two different controllers. The only way to specify the controller, is through base-uri and parameters are not part of it.
Spring determines the method by 2 parameters: Mapping and HTTP method. There is no way, unless you modify Spring manually, to allow 3rd parameter in this case. Also, there is no 3rd parameter.
Alternatively, you can have 3rd controller with Mapping, that calls other 2 controllers when "/overview" endpoint is triggered. In that case, you need to remove the mapping from tweet and user - controllers.

Is there a way to access a PathVariable in a controller level #PreAuthorize annotation?

is there a way to access the httpRequest within a controller level #PreAuthorize annotation to obtain a PathVariable and use it for the expression inside the #PreAuthorize annotation?
I want to be able to do the following, where #somePathVariable would result in the actual value passed for the PathVariable:
#RequestMapping(value = "/{somePathVariable}/something")
#PreAuthorize("#someBean.test(#somePathVariable)")
public class SomeController { ... }
It also would be sufficient if i could access the HttpServletRequest and get the PathVariable manually.
Please note that this expression is at the controller level before answering. I'd appreciate any help!
So as #pvpkiran already commented. It's probably not possible to get the param the way i want. However his workaround with using a bean to access the PathVariables manually seems to work just fine.
#Component
#RequiredArgsConstructor
public class RequestHelper {
private final HttpServletRequest httpServletRequest;
/* https://stackoverflow.com/questions/12249721/spring-mvc-3-how-to-get-path-variable-in-an-interceptor/23468496#23468496 */
public Object getPathVariableByName(String name) {
final Map pathVariables = (Map) httpServletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
return pathVariables.get(name);
}
}
and
#RequestMapping(value = "/{somePathVariable}/something")
#PreAuthorize("#someBean.test(#requestHelper.getPathVariableByName('somePathVariable'))")
public class SomeController { ... }
did the job. It's not perfect but it works. The other (prob. better) option is to use #PreAuthorize on method level.
Thanks for your help!

Spring #SessionAttributes shared between controllers?

I'm using Spring 4.3.7, and I got two form controllers with forms rendering using Spring form taglib in corresponding views. To preserve form data between requests (for rendering invalid forms) I store them in SessionAttributes.
LabCreateController:
#Controller
#RequestMapping(path = "/labs/create")
#SessionAttributes("form")
public class LabCreateController {
#ModelAttribute("form")
public LabCreateForm form() {
return new LabCreateForm();
}
#GetMapping
public String showForm() {
return "lab_create";
}
}
WallController:
#Controller
#RequestMapping(path = "/group/{id}/wall")
#SessionAttributes("form")
public class WallController {
#ModelAttribute("form")
public PostCreateForm form() {
return new PostCreateForm();
}
#GetMapping(path = "/new")
public String newPostGet() {
return "communities_newpost";
}
}
I open /labs/create in browser, everything is fine. Then I open /group/4/wall/new and get a following error:
Invalid property 'text' of bean class
[...LabCreateForm]
i.e it means that attribute form from LabCreateController somehow passed to WallController, though Spring documentation says:
Session attributes as indicated using this annotation correspond to a
specific handler's model attributes.
I believe it means they shouldn't be shared between controllers. Also this answer says that it is so since Spring 3.
Is it a bug or I'm missing something? If not, what is the appropriate way of storing a form inside one controller?

How to ensure mandatory parameters are passed into a Spring MVC controller method?

Given a Spring-MVC controller method:
#RequestMapping(value = "/method")
public void method(#RequestParam int param1,
#RequestParam int param2) { /*...*/ }
If parameters are missing in the request URL, an error is reported, e.g:
JBWEB000068: message Required int parameter 'param1' is not present
I need to move the parameters into a single model class yet keep exactly the same GET request URL. So have modified method's parameter to MyModel model, which contains param1 and param2.
This works if #RequestParam is omitted but the snag is no error is reported if parameters are missing. If, on the other hand #RequestParam is included, a parameter named "model" is expected in the GET request. Is there a way to make model parameters mandatory yet keep the same request URL?
Use JSR-303 annotations to validate the object (and don't use primitives but the Object representations in that case).
public class MyObject {
#NotNull
private Integer param1;
#NotNull
private Integer param2;
// Getters / Setters
}
Controller method.
#RequestMapping(value = "/method")
public void method(#Valid MyObject obj) { /*...*/ }
If you don't have a JSR-303 provider (hibernate-validator for instance) on your classpath create a Validator and use this to validate your object.
Links.
Spring MVC reference guide
Spring Validation reference guide
You can try #ModelAttribute and method=RequestMethod.POST:
In your controller:
//...
#RequestMapping(value="/method", method=RequestMethod.POST)
public String add(#ModelAttribute("modelName") ModelClass form)
{
//.. your code
}
//..
In your JSP:
<%# taglib uri="http://www.springframework.org/tags/form" prefix="f" %>
//.. some code
<f:form action="method" method="post" modelAttribute="modelName">
</f:form>
If you are restricted to GET requests only, you might not be able to use a separate ModelClass. You need to resort to request parameters in that case.

Call Spring bean method in JSP

I have a java class
LayoutManager.java, which I pass as a Sprean Bean into my jsp page using
<custom:useSpringBean var="layoutManager" bean="LayoutManager"/>
How do I call methods from LayoutManager.java inside my jsp using the spring bean?
I feel like I would use some form of servlets <% %>, but not sure about the syntax
I want to call the method
public Iterable<Layouts> getSpecificLayout(String subjectName)
The only spring code I have right now is
public class UseSpringBean extends SimpleTagSupport
{
public void doTag() throws JspException, IOExceptionP
PageContext pageContext = (PageContext)getJspContext();
WebApplicationContext springContext = WebApplicationContextUtils.getWebApplicationContext(pageContext.getServletContext());
pageContext.setAttribute(var, springContext.getBean(bean), PageContxt.PAGE_SCOPE);
}
If you really want the list of Layouts to be used in the jsp page then from the spring controller you need to add this list in the ModelMap so that its visible in the jsp page.
Something like:
#RequestMapping(value="/getSpecificLayout", method = RequestMethod.GET)
public String getSpecificLayout(Stirng subjectName, ModelMap model){
Iterable<Layouts> layouts = getSpecificLayout(String subjectName);
model.addAttribute("layouts", layouts);
return "listLayouts";
}
in jsp:
<c:for items="listLayouts" var="layout">
<c:out value="layout.name"/>
</c:for>
(This is not a tested code just a sample. Sorry somehow the code editing is not working, I mean I am not able to see the inline editor).

Categories