I want to create multiple servlets to the /persons path. The path without any query parameters should just serve the html page (using Thymeleaf templating).
The one with parameters should instead populate the http model with data.
#Controller
#RequestMapping("/persons")
public class PricingCacheController {
//default just routing to the html template
#GetMapping(params = "")
public String personsHtml() {
System.out.println("initial page load");
return "persons";
}
#GetMapping
public String personsGet(Model model, #RequestParam MyBean bean) {
System.out.println("get request");
model.addAttribute("dto", dao.findAll());
return "persons";
}
When executing localhost:8080/persons, I get a 404 not found.
You cannot do this, servlet mapping is done on the url level and query parameters are ignored when parsing the url so since you have to mappings for the same path (regardless the query parameters) you will get 404, why not use path variables?
Related
#RequestMapping("/accounts")
public class controller {
#GetMapping("/get/{id}")
public final ResponseEntity<?> getHandler(){
}
#PostMapping(value = "/create")
public final ResponseEntity<?> createHandler(){
/*
trying to use some spring library methods to get the url string of
'/accounts/get/{id}' instead of manually hard coding it
*/
}
}
This is the mock code, now I am in createHandler, after finishing creating something, then I want to return a header including an URL string, but I don't want to manually concat this URL string ('/accounts/get/{id}') which is the end point of method getHandler(), so I am wondering if there is a method to use to achieve that? I know request.getRequestURI(), but that is only for the URI in the current context.
More explanation: if there is some library or framework with the implementation of route:
Routes.Accounts.get(1234)
which return the URL for the accounts get
/api/accounts/1234
The idea is, that you don't need to specify get or create (verbs are a big no-no in REST).
Imagine this:
#RequestMapping("/accounts")
public class controller {
#GetMapping("/{id}")
public final ResponseEntity<?> getHandler(#PathVariable("id") String id) {
//just to illustrate
return complicatedHandlerCalculation(id).asResponse();
}
#PostMapping
public final ResponseEntity<?> createHandler() {
//return a 204 Response, containing the URI from getHandler, with {id} resolved to the id from your database (or wherever).
}
}
This would be accessible like HTTP-GET: /api/accounts/1 and HTTP-POST: /api/accounts, the latter would return an URI for /api/accounts/2 (what can be gotten with HTTP-GET or updated/modified with HTTP-PUT)
To resolve this URI, you could use reflection and evaluate the annotations on the corresponding class/methods like Jersey does.
A Spring equivalent could be:
// Controller requestMapping
String controllerMapping = this.getClass().getAnnotation(RequestMapping.class).value()[0];
and
//Method requestMapping
String methodMapping = new Object(){}.getClass().getEnclosingMethod().getAnnotation(GetMapping.class).value()[0];
taken from How do i get the requestmapping value in the controller?
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?
I have a Spring MVC Controller returning a page with an attribute as followed
#RequestMapping(method = RequestMethod.GET, value = "/create")
public ModelAndView getAddAccountView() {
ModelAndView model = new ModelAndView("protected/accounts/AccountAddView");
List<Client> clients=clientService.findAll();
model.addObject("listClients", clients);
return model;
}
Client is a #Entity
in my AccountAddView.jsp file, i'm trying to use the ng-init as follow:
<div class="row-fluid" ng-controller="accountsController as ctrl" ng-init="clients=${listClients}">
and in my app.js, in my controller, i try to access the list of client as followed
var listOfClients=$scope.clients;
but I'm still getting the following error
angular.min-1.5.3.js:116 Error: [$parse:lexerr] http://errors.angularjs.org/1.5.3/$parse/lexerr?p0=Unexpected%20nextharacter%20&p1=s%2033-33%20%5B%40%5D&p2=clients%3D%5Bsoftbank.ui.model.Client%4042%2C%softbank.ui.model.Client%4041%2C%softbank.ui.model.Client%4043%5D
at Error (native)
at http://localhost:8080/softbank/resources/js/angular.min-1.5.3.js:6:416
at gc.throwError (http://localhost:8080/softbank/resources/js/angular.min-1.5.3.js:212:149)
at gc.lex (http://localhost:8080/softbank/resources/js/angular.min-1.5.3.js:211:16)
at Object.ast (http://localhost:8080/softbank/resources/js/angular.min-1.5.3.js:216:103)
at Object.compile (http://localhost:8080/softbank/resources/js/angular.min-1.5.3.js:225:232)
at hc.parse (http://localhost:8080/softbank/resources/js/angular.min-1.5.3.js:252:380)
at e (http://localhost:8080/softbank/resources/js/angular.min-1.5.3.js:123:317)
at m.$eval (http://localhost:8080/softbank/resources/js/angular.min-1.5.3.js:142:463)
at pre (http://localhost:8080/softbank/resources/js/angular.min-1.5.3.js:271:500)
please what is wrong here. why is ng-init generating this errors?
thanks in advance for your answer.
I'm just starting with Angular with Spring so what I'll do is explain how I did it and you can see if it's a viable option for you.
For one, I didn't try to go through the ModelAndView for my data. I let my "regular" controller return the view but got my data via the angular controller and service along with a #RestController on the Spring side (that's two separate Spring controllers then).
To return only the view you have at least two options that I'm aware of. One, you can just return a ModelAndView without a model like so:
public ModelAndView yourView() {
return new ModelAndView("yourView");
}
Or two, return the name of the view as a string like so:
public String yourView() {
return "yourView";
}
In both cases you'd obviously need the correct #RequestMapping.
In my view I had an angular controller function that made a call to my associated angular service which in turn called my #RestController and so on. I initiated the data like so:
ng-controller="MyController as ctrl" ng-init="ctrl.allThings()"
Examples of the code:
Angular controller:
self.allThings = function () {
ThingService.things()
.then(
function (data) {
self.things = data;
},
function (errResponse) {
console.error("Error retrieving thing list");
}
);
};
Angular service:
things: function() {
return $http.get('/things')
.then(
function(response) {
return response.data;
},
function (errResponse) {
return $q.reject(errResponse);
}
);
},
Spring REST controller:
#RequestMapping(value = "/things", method = RequestMethod.GET)
public List<Thing> things() {
return thingService.findAll();
}
I imagine you know the remaining code for the Spring side. Jackson will handle all the object conversions for you.
Old Post but still I would like to answer so that anyone who is using Angular with Spring MVC get some help.
Using model attribute is a challenge with Angular. Earlier I was setting employee data in model attribute like mav.addObject("employee",employee), but when I was trying to use ${employee} inside the jsp, Angular wasn't able to set the java object using ng-init directive because it was expecting a java script object. So I set json inside the model Attribute mav.addObject("employeeJson", empJson) and parsed it using json.Parse() to convert it into Java Script Object and was able to use it with Angular expressions on the jsp Below are the steps which I followed:
1. Spring Controller
In your controller, set json in the model attribute. For e.g.
mav.addObject("employeeJson", empJson);
Make sure before adding it to ModelAttribute, replace quotes with html entity " otherwise there will be error while parsing the json
empJson.replaceAll("\"", "\&\quot;");
2. Jsp
Use angular init directive, data-ng-init="yourController.getEmployeeData('${employeeJson}')"
3. In Angular js parse this json to get the json
var vm = this;
vm.employee = {};
vm.getEmployeeData = function(employeeJson){,
vm.employee = JSON.parse(employeeJson);
};
4. Inside jspI can access employee name as
{{yourController.employee.firstName}}
I have a requirement where the back-end Spring controller is expecting certain values in the request header, i.e certain custom headers.
The view, a jsp has a html form which will submit the parameters from the end user to the controller. Since it is not possible to send custom headers from an html form or javascript function without using XMLHttpHeader. I cannot use XMLHttpHeader in this case since that will be an AJAX call and in my case i want a form submit.
The only option left for me is, add a mediator servlet which will intercept the call from the jsp, read the values from the request parameter and then add them as request headers and forward them to the final controller. Since it is not possible to add custom header in HttpServletRequest, i used HttpServletRequestWrapper as given in the
example.
The problem is to forward it to the Spring controller , the following code
MockHttpRequestWrapper req = new MockHttpRequestWrapper(request);
req.addHeader("REMOTE_USER", ssoId);
req.addHeader("REALM", realmId);
RequestDispatcher dispatcher = request.getRequestDispatcher(url);
dispatcher.forward(req, response);
where MockHttpRequestWrapper is a type of HttpServletRequestWrapper as per the example
public class MockHttpRequestWrapper extends HttpServletRequestWrapper {
private Map customHeaderMap = null;
public MockHttpRequestWrapper(HttpServletRequest request) {
super(request);
customHeaderMap = new HashMap();
}
public void addHeader(String name,String value){
customHeaderMap.put(name, value);
}
#Override
public String getParameter(String name) {
String paramValue = super.getParameter(name); // query Strings
if (paramValue == null) {
paramValue = (String) customHeaderMap.get(name);
}
return paramValue;
}
}
I haven't tested the sample with Filters but i expected the same to work with RequestDispatchers. The final controller is called but the headers are not to be found.
Is the same Request object not passed to the controller? IN the controller the code
request.getHeader("REMOTE_USER")
returns null.
Looking forward to answers to expand my understanding on the subject :)
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.