So I have a controller with #RequestMapping(value = { "/something", "/otherThing" })
I just can't seem to figure out how I can determine inside my controller as to which one of the paths are being served right now, "something" or the "otherThing".
Breaking down the controller and making 2 separate ones is not an option in my case.
Any ideas how I could go about determining the path being served inside a controller's method?
#RequestMapping(value = { "/something", "/otherThing" })
public void polymorpHandlerMethod(HttpServletRequest request) {
if (request.getContextPath().startsWith("/something")) {
// do stuff
} else if (request.getContextPath().startsWith("/otherThing")) {
// do more stuff
}
}
You can get the url using the below code and can get the path from it
URL url = new URL(request.getRequestURL().toString());
String path = url.getPath();
You can check this path and use it in your logic
Hope it will help your senario.
Related
I think in terms of REST, the ID should be placed into the URL, something like:
https://example.com/module/[ID]
and then I call GET, PUT, DELETE on that URL. That's kind of clear I think. In Spring MVC controllers, I'd get the ID with #PathVariable. Works.
Now, my practical problem with Spring MVC is, that if I do this, I have to NOT include the ID as part of the form (as well), Spring emits warnings of type
Skipping URI variable 'id' since the request contains a bind value with the same name.
otherwise. And it also makes kind of sense to only send it once, right? What would you do if they don't match??
That would be fine, but I do have a custom validator for my form backing bean, that needs to know the ID! (It needs to check if a certain unique name is already being used for a different entity instance, but cannot without knowing the ID of the submitted form).
I haven't found a good way to tell the validator that ID from #PathVariable, since the validation happens even before code in my controller method is executed.
How would you solve this dilemma?
This is my Controller (modified):
#Controller
#RequestMapping("/channels")
#RoleRestricted(resource = RoleResource.CHANNEL_ADMIN)
public class ChannelAdminController
{
protected ChannelService channelService;
protected ChannelEditFormValidator formValidator;
#Autowired
public ChannelAdminController(ChannelService channelService, ChannelEditFormValidator formValidator)
{
this.channelService = channelService;
this.formValidator = formValidator;
}
#RequestMapping(value = "/{channelId}/admin", method = RequestMethod.GET)
public String editChannel(#PathVariable Long channelId, #ModelAttribute("channelForm") ChannelEditForm channelEditForm, Model model)
{
if (channelId > 0)
{
// Populate from persistent entity
}
else
{
// Prepare form with default values
}
return "channel/admin/channel-edit";
}
#RequestMapping(value = "/{channelId}/admin", method = RequestMethod.PUT)
public String saveChannel(#PathVariable Long channelId, #ModelAttribute("channelForm") #Valid ChannelEditForm channelEditForm, BindingResult result, Model model, RedirectAttributes redirectAttributes)
{
try
{
// Has to validate in controller if the name is already used by another channel, since in the validator, we don't know the channelId
Long nameChannelId = channelService.getChannelIdByName(channelEditForm.getName());
if (nameChannelId != null && !nameChannelId.equals(channelId))
result.rejectValue("name", "channel:admin.f1.error.name");
}
catch (EmptyResultDataAccessException e)
{
// That's fine, new valid unique name (not so fine using an exception for this, but you know...)
}
if (result.hasErrors())
{
return "channel/admin/channel-edit";
}
// Copy properties from form to ChannelEditRequest DTO
// ...
// Save
// ...
redirectAttributes.addFlashAttribute("successMessage", new SuccessMessage.Builder("channel:admin.f1.success", "Success!").build());
// POST-REDIRECT-GET
return "redirect:/channels/" + channelId + "/admin";
}
#InitBinder("channelForm")
protected void initBinder(WebDataBinder binder)
{
binder.setValidator(formValidator);
}
}
I think I finally found the solution.
As it turns out Spring binds path variables to form beans, too! I haven't found this documented anywhere, and wouldn't have expected it, but when trying to rename the path variable, like #DavidW suggested (which I would have expected to only have a local effect in my controller method), I realized that some things got broken, because of the before-mentioned.
So, basically, the solution is to have the ID property on the form-backing object, too, BUT not including a hidden input field in the HTML form. This way Spring will use the path variable and populate it on the form. The local #PathVariable parameter in the controller method can even be skipped.
The cleanest way to solve this, I think, is to let the database handle the duplicates: Add a unique constraint to the database column. (or JPA by adding a #UniqueConstraint)
But you still have to catch the database exception and transform it to a user friendly message.
This way you can keep the spring MVC validator simple: only validate fields, without needing to query the database.
Could you not simply disambiguate the 2 (URI template variables vs. parameters) by using a different name for your URI template variable?
#RequestMapping(value = "/{chanId}/admin", method = RequestMethod.PUT)
public String saveChannel(#PathVariable Long chanId, #ModelAttribute("channelForm") #Valid ChannelEditForm channelEditForm, BindingResult result, Model model, RedirectAttributes redirectAttributes)
{
[...]
What ever you said is correct the correct way to design rest api is to mention the resource id in path variable if you look at some examples from the swagger now as open api you could find similar examples there
for you the correct solution would be to use a custom validator like this
import javax.validation.Validator;`
import org.apache.commons.lang3.StringUtils;`
import org.springframework.validation.Errors;`
importorg.springframework.validation.beanvalidation.CustomValidatorBean;`
public class MyValidator extends CustomValidatorBean {`
public void myvalidate(Object target,Errors errors,String flag,Profile profile){
super.validate(target,errors);
if(StringUtils.isEmpty(profile.name())){
errors.rejectValue("name", "NotBlank.profilereg.name", new Object[] { "name" }, "Missing Required Fields");
}
}
}
This would make sure all the fields are validated and you dont need to pass the id in the form.
That defines multi route method in controller in Spring MVC
#RequestMapping(value={"/path", "/path2"}, method = RequestMethod.GET)
public String MyMethod () {
// Determine which route invoked the method
return null;
}
Is there a way to determine which route invoked the method?
Appreciate your kind help.
You could use HttpServletRequest which has a method called getRequestURL() to retrieve the actual URL, allowing you to parse which path was used.
However, another possibility is using path variables instead:
#RequestMapping(value = "/{path}", method = RequestMethod.GET)
public String myMethod(#PathVariable String path) {
// Do stuff with "path"
return null;
}
In this case, the path variable will contain whatever you enter matching the path given in your #RequestMapping, in your case it would be "path" or "path2". However, this will also allow other path variables as well ("path3" for example, ...), so you might want to validate it first before using.
I believe you can use HttpServletRequest:
#RequestMapping(value={"/path.html", "/path2.html"}, method = RequestMethod.GET)
public String MyMethod (HttpServletRequest request) {
// Determine which route invoked the method
String url = new String(request.getRequestURL());
log.debug("URL: " + url); //use whatever you use to log
return null;
}
I want to use the following type of URL in Restlet: http://url.com/http://www.anotherurl.com/path
As a result I want to get http://www.anotherurl.com/path as a parameter.
However it does nothing.
Also, if I use http://url.com/path , then I receive "path" without problems. http://url.com/www.anotherurl.com gives me www.anotherurl.com. However http://url.com/www.anotherurl.com/path is 404.
You need to encode the parameter special characters properly. Use URLEncoder to do so.
In fact, there are two parts here.
The URL building using the Reference class:
Reference ref = new Reference();
ref.setScheme("http");
ref.setHostDomain("localhost");
ref.setHostPort(8182);
ref.addSegment("test");
ref.addSegment("http://test");
ClientResource cr = new ClientResource(ref);
cr.get();
Getting the value as a path parameter and decode it. Here is the routing configuration in the application class:
public Restlet createInboundRoot() {
Router router = new Router(getContext());
router.attach("/test/{id}", MyServerResource.class);
return router;
}
And the corresponding code in the server resource:
public class MyServerResource extends ServerResource {
#Get
public Representation get() {
String id = getAttribute("id");
// Prints http%3A%2F%2Ftest
System.out.println("id = "+id);
// Prints http://test
System.out.println("id = "+Reference.decode(id));
return new EmptyRepresentation();
}
}
Hope it helps you,
Thierry
I've a webservice similar to the following:
#RequestMapping(value = "/getMovies", method = RequestMethod.POST, produces = "application/json")
public #ResponseBody ResponseVO getMoviesList(#RequestBody RequestVO vo) { .... }
The RequestVO class is :
public class RequestVO {
private String[] genreList;
public void updateRequest() {
if (genreList != null) {
// remove the duplicates from the list
// or something else
}
}
public String[] getGenreList() {
return genreList;
}
public void setGenreList(String[] genreList) {
this.genreList = genreList;
}
}
Now I want the method updateRequest to be called automatically after the request json is processed as RequestVO. One thing I currently think of is #PostConstruct, but seems to be of no use in this case.
My question is does Spring provide any such annotation or mechanism ? Or #PostConstruct will do the trick ?
NB : I don't need workarounds as I've plenty of them. So please refrain yourself from posting them. Again above codes are mere samples (please ignore minor mistakes).
Couple of thins to consider:
Don't use verbs in Rest Service method names (like getMovies) because you specify action using HTTP verbs like GET, POST and so on.
POST should be used to create a resource on the server not to retrieve them (what is implied by the method name: 'getMovies')
What do you want to achieve is RequestVO.updateRequest() invoked before passing RequestVO instance to getReportData(), is it right? If so, could you elaborate, why can't you invoke this method on the beginning of the getReportData()?
If you want to achieve this kind of functionality despite the fact it's sensible or not, try:
create new aspect which will be invoked before getReportData() and invoke updateRequest()
use #JsonFactory (provided you use Jackson to map JSON to Java objects) like:
public class RequestVO {
private String[] genreList;
public void updateRequest() {
if (genreList != null) {
...
}
}
public String[] getGenreList() {
return genreList;
}
public void setGenreList(String[] genreList) {
this.genreList = genreList;
}
#JsonFactory
public static RequestVO createExample(#JsonProperty("genreList") final String[] genreList) {
RequestVO request = new RequestVO(genreList);
request.updateRequest();
return request;
}
}
As you are saying, #PostConstruct is only call after a bean creation and is of no use here. But you have 2 simple ways of calling a method after the end of another method.
explicit : just wrap your real method in another one, and do all pre- or post-processing there
#RequestMapping(value = "/getMovies", method = RequestMethod.POST, produces = "application/json")
public #ResponseBody ResponseVO getMoviesList(#RequestBody RequestVO vo) {
// pre_processing
ResponseVO resul = doGetMoviesList(vo);
// post_processing
return resul;
}
public ResponseVO doGetMoviesList(RequestVO vo) { ... }
Is is simple to write, even if not very nice.
use Spring AOP. You can define an after returning advice that will be called after the advised method returns normally. The advice can be shared across multiple classes if you need it and write your pointcut accordingly. It is really powerfull, but has one caveat : Spring implementation uses proxies and by default JDK proxies. That means that any advised method should be member of an interface and called through that interface. So it would be much simpler and cleaner to advise a service than a controller. IMHO, if you really need to do AOP on a controller, you should use full AspectJ including class weaving ... In short, it is very nice, very powerfull, but a little harder to implement.
Just startred using restlet with java and was pleasently surprised how easy it was. However this was with puts. I then started to work with get but couldn't work out how to pass infromation with the get.
With the put it was easy as:
#Put
public Boolean store(Contact contact);
But when i try and do this with get it doesnt work. From reading around i think i have to not pass it any parameters and just have this:
#Get
public Contact retrieve();
and then pass the parameters in a url or something?
But i cant find any info on how to do this. As with put i could just use:
resource.store(user1);
Any help please?
Im pretty sure this is the kind of thing i just need to see an example of and then ill be able to do it easily. Example of how to get the infromation out of the url at the other side would be very helpful aswell.
Thanks
I now have on my client side:
String username = "tom";
ClientResource cr2 = new ClientResource("http://.../ContactManager/contacts/" + username);
ContactResource resource2 = cr2.wrap(ContactResource.class);
resource2.logIn();
On the server side i have:
#Get
public Contact logIn(){
System.out.println("name is " + resource.getAttributes().get("contactId"));
return null;
}
But i am not sure what resource is? It doesnt exist in my program and am not sure what type it needs to be or where to declare it.
A good approach with REST is to specify this contact id within the URI. Something like that: /contacts/mycontactid.
When attaching your resources within the application class, you can define this segment as an attribute (the contact id one in your case).
public class ContactsApplication extends Application {
public Restlet createInboundRoot() {
Router router = new Router(getContext());
router.attach("/contacts/", ContactsServerResource.class);
router.attach("/contacts/{contactId}", ContactServerResource.class);
return router;
}
}
Then you can have the code provided by Richard in his answer.
Hope it helps you.
Thierry
I know this question was asked along time ago but the answer I think you are looking for is:
Application Code
public class ContactsApplication extends Application {
public Restlet createInboundRoot() {
Router router = new Router(getContext());
router.attach("/user/", ContactsServerResource.class);
router.attach("/user/{user}", ContactServerResource.class);
return router;
}
}
Resource Code
#Get
public void login()
String userName = (String)this.getRequestAttributes().get("user");
The (String)this.getRequestAttributes().get("user"); allows you extract details from the URL.
Hope this helps
It seems that what you are looking for is something like:
public final Representation get() {
String contactId = request.getAttributes().get("contactId"));
// Find the Contact object with that id
JacksonRepresentation<Contact> result =
new JacksonRepresentation<Contact>(contact);
return result;
}
Also see: how to pass parameters to RESTlet webservice from android? for a similar approach.