In the controller , i have this code,
somehow, i want to get the request Mapping value "search".
How is it possible ?
#RequestMapping("/search/")
public Map searchWithSearchTerm(#RequestParam("name") String name) {
// more code here
}
If you want the pattern, you can try HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE:
#RequestMapping({"/search/{subpath}/other", "/find/other/{subpath}"})
public Map searchWithSearchTerm(#PathVariable("subpath") String subpath,
#RequestParam("name") String name) {
String pattern = (String) request.getAttribute(
HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
// pattern will be either "/search/{subpath}/other" or
// "/find/other/{subpath}", depending on the url requested
System.out.println("Pattern matched: "+pattern);
}
It seems you are looking for the path that this request has matched, then you can directly get it from servlet path
#RequestMapping("/search/")
public Map searchWithSearchTerm(#RequestParam("name") String name, HttpServletRequest request) {
String path = request.getServletPath();
// more code here
}
Having a controller like
#Controller
#RequestMapping(value = "/web/objet")
public class TestController {
#RequestMapping(value = "/save")
public String save(...) {
....
}
}
You cant get the controller base requestMapping using reflection
// Controller requestMapping
String controllerMapping = this.getClass().getAnnotation(RequestMapping.class).value()[0];
or the method requestMapping (from inside a method) with reflection too
//Method requestMapping
String methodMapping = new Object(){}.getClass().getEnclosingMethod().getAnnotation(RequestMapping.class).value()[0];
Obviously works with an in requestMapping single value.
Hope this helps.
#RequestMapping("foo/bar/blub")
public Map searchWithSearchTerm(#RequestParam("name") String name, HttpServletRequest request) {
// delivers the path without context root
// mapping = "/foo/bar/blub"
String mapping = request.getPathInfo();
// more code here
}
For Spring 3.1 and above you can use ServletUriComponentsBuilder
#RequestMapping("/search/")
public ResponseEntity<?> searchWithSearchTerm(#RequestParam("name") String name) {
UriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentRequest();
System.out.println(builder.buildAndExpand().getPath());
return new ResponseEntity<String>("OK", HttpStatus.OK);
}
Related
I've got an ASP.NET MVC website that needs to display user-provided URLs stored in the DB. The way they're displayed will be different depending on how that URL would be routed if that URL refers to the website itself.
For example, supposing the website is foo.com:
URL stored in DB: foo.com/pie/3/nutrition
Controller is "pie"
Action is "nutrition"
ID is 3
The way the link is formatted depends on all three of these.
How would I extract this information correctly? Can I query the URL routing device?
Note: "Use a regular expression" type of answers don't interest me -- the site, action, or controller names could change, the website may be accessible through multiple site names and ports, etc...
You may find the RouteInfo class illustrated in this blog post useful:
public class RouteInfo
{
public RouteData RouteData { get; private set; }
public RouteInfo(Uri uri, string applicationPath)
{
RouteData = RouteTable.Routes.GetRouteData(new InternalHttpContext(uri, applicationPath));
}
private class InternalHttpContext : HttpContextBase
{
private readonly HttpRequestBase request;
public InternalHttpContext(Uri uri, string applicationPath)
{
this.request = new InternalRequestContext(uri, applicationPath);
}
public override HttpRequestBase Request
{
get { return this.request; }
}
}
private class InternalRequestContext : HttpRequestBase
{
private readonly string appRelativePath;
private readonly string pathInfo;
public InternalRequestContext(Uri uri, string applicationPath)
{
this.pathInfo = uri.Query;
if (string.IsNullOrEmpty(applicationPath) || !uri.AbsolutePath.StartsWith(applicationPath, StringComparison.OrdinalIgnoreCase))
{
this.appRelativePath = uri.AbsolutePath.Substring(applicationPath.Length);
}
else
{
this.appRelativePath = uri.AbsolutePath;
}
}
public override string AppRelativeCurrentExecutionFilePath
{
get { return string.Concat("~", appRelativePath); }
}
public override string PathInfo
{
get { return this.pathInfo; }
}
}
}
You could use it like this:
public ActionResult Index()
{
Uri uri = new Uri("http://foo.com/pie/3/nutrition");
RouteInfo routeInfo = new RouteInfo(uri, this.HttpContext.Request.ApplicationPath);
RouteData routeData = routeInfo.RouteData;
string controller = routeData.GetRequiredString("controller");
string action = routeData.GetRequiredString("action");
string id = routeData.Values["id"] as string;
...
}
From the section about unit testing in this: scott guthrie blog post
you can do something like this:
MockHttpContext httpContxt = new MockHttpContext("foo.com/pie/3/nutrition");
RouteData routeData = new routes.GetRouteData(httpContext);
where routes is the RouteCollection you used to initialize your routes in your application. Then you can interrogate routeData["controller"] etc
the post is about an early version of MVC, so the class names may have changed since then.
I have a method in my controller that should returns a String in JSON. It returns JSON for non primitive types:
#RequestMapping(value = "so", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<String> so() {
return new ResponseEntity<String>("This is a String", HttpStatus.OK);
}
The curl response is:
This is a String
The root of the problem is that Spring (via ResponseEntity, RestController, and/or ResponseBody) will use the contents of the string as the raw response value, rather than treating the string as JSON value to be encoded. This is true even when the controller method uses produces = MediaType.APPLICATION_JSON_VALUE, as in the question here.
It's essentially like the difference between the following:
// yields: This is a String
System.out.println("This is a String");
// yields: "This is a String"
System.out.println("\"This is a String\"");
The first output cannot be parsed as JSON, but the second output can.
Something like '"'+myString+'"' is probably not a good idea however, as that won't handle proper escaping of double-quotes within the string and will not produce valid JSON for any such string.
One way to handle this would be to embed your string inside an object or list, so that you're not passing a raw string to Spring. However, that changes the format of your output, and really there's no good reason not to be able to return a properly-encoded JSON string if that's what you want to do. If that's what you want, the best way to handle it is via a JSON formatter such as Json or Google Gson. Here's how it might look with Gson:
import com.google.gson.Gson;
#RestController
public class MyController
private static final Gson gson = new Gson();
#RequestMapping(value = "so", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<String> so() {
return ResponseEntity.ok(gson.toJson("This is a String"));
}
}
#RequestMapping(value = "so", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody String so() {
return "This is a String";
}
import org.springframework.boot.configurationprocessor.json.JSONException;
import org.springframework.boot.configurationprocessor.json.JSONObject;
public ResponseEntity<?> ApiCall(#PathVariable(name = "id") long id) throws JSONException {
JSONObject resp = new JSONObject();
resp.put("status", 0);
resp.put("id", id);
return new ResponseEntity<String>(resp.toString(), HttpStatus.CREATED);
}
An alternative solution is to use a wrapper for the String, for instances this:
public class StringResponse {
private String response;
public StringResponse(String response) {
this.response = response;
}
public String getResponse() {
return response;
}
}
Then return this in your controller's methods:
ResponseEntity<StringResponse>
Using Spring 3.2.3, I'm trying to implement a simple CRUD controller that handles REST-ful URLs. It relies on a PropertyEditor to convert a path variable to a BusinessService entity by loading it from an application service. Code is as follows:
#Controller
public class BusinessServiceController {
#Autowired
private BusinessServiceService businessSvcService;
public BusinessServiceController() {
}
#InitBinder
public void initBinder(final WebDataBinder binder) {
binder.registerCustomEditor(BusinessService.class, new BusinessServicePropertyEditor(businessSvcService));
}
#RequestMapping(value = "/ui/account/business-services/{businessSvc}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ModelAndView update(#ModelAttribute("businessSvc") #Valid final BusinessService businessSvc, final BindingResult result,
final RedirectAttributes redirectAttribs) throws UnknownBusinessServiceException {
ModelAndView mav;
if (result.hasErrors()) {
mav = new ModelAndView("/business-service/edit");
}
else {
businessSvcService.updateBusinessService(XSecurity.principal().getId(), businessSvc);
mav = new ModelAndView("redirect:/ui/account/business-services");
redirectAttribs.addFlashAttribute("message", Message.info("businessService.updated", businessSvc.getTitle()));
}
return mav;
}
}
public class BusinessServicePropertyEditor extends PropertyEditorSupport {
private final BusinessServiceService businessSvcService;
public BusinessServicePropertyEditor(final BusinessServiceService businessSvcService) {
this.businessSvcService = businessSvcService;
}
#Override
public String getAsText() {
final BusinessService svc = (BusinessService) getValue();
return Long.toString(svc.getId());
}
#Override
public void setAsText(final String text) {
final BusinessService svc = businessSvcService.getBusinessService(Long.parseLong(text));
setValue(svc);
}
}
According to SPR-7608, starting from Spring 3.2, #ModelAttribute method argument resolution checks if a path variable by the same name exists (it does here), in which case it tries to convert that path variable's value to the target parameter type through registered Converters and PropertyEditors. This is not what I'm experiencing. When I inspect what ServletModelAttributeMethodProcessor does, it clearly uses the request DataBinder's ConversionService to perform type conversion, which does not consider registered PropertyEditors, and hence BusinessServicePropertyEditor#setAsText is never called.
Is this a configuration problem or an actual bug?
Thanks for your help!
Spring's ConversionService and Converters are replacement for standard Java Beans PropertyEditors.
You need to implement Converter instead of PropertyEditor if this feature is based purely on conversion service.
To register your custom converters in WebDataBinder you might use ConfigurableWebBindingInitializer or #InitBinder method.
In our web app, using Spring MVC 3.2 we display many paginated lists of different objects, and the links to other pages in the list are constructed like this:
/servlet/path?pageNum=4&resultsPerPage=10&sortOrder=ASC&sortBy=name
although there might be additional request parameters in the URL as well (e.g., search filters).
So we have controller methods like this:
#RequestMapping(method = RequestMethod.GET, value="/ajax/admin/list")
public String ajaxlistGroups(Model model,
#RequestParam(value="pageNumber",required=false,defaultValue="0") Long pageNumber,
#RequestParam(value="resultsPerPage",required=false,defaultValue="10") int resultsPerPage,
#RequestParam(value="sortOrder",required=false,defaultValue="DESC") String sortOrder,
#RequestParam(value="orderBy",required=false,defaultValue="modificationDate")String orderBy) {
// create a PaginationCriteria object to hold this information for passing to Service layer
// do Database search
// return a JSP view name
}
so we end up with this clumsy method signature, repeated several times in the app, and each method needs to create a PaginationCriteria object to hold the pagination information, and validate the input.
Is there a way to create our PaginationCriteria object automatically, if these request params are present? E.g., replace the above with:
#RequestMapping(method = RequestMethod.GET, value="/ajax/admin/list")
public String ajaxlistGroups(Model model, #SomeAnnotation? PaginationCriteria criteria,
) {
...
}
I.e., is there a way in Spring to take a defined subset of requestParams from a regular GET request, and convert them to an object automatically, so it's available for use in the Controller handler method? I've only used #ModelAttribute before, and that doesn't seem the right thing here.
Thanks!
Spring 3.2 should automatically map request parameters to a custom java bean.
#RequestMapping(method = RequestMethod.GET, value="/ajax/admin/list")
public String ajaxlistGroups(Model model, PaginationCriteriaBean criteriaBean,
) {
//if PaginationCriteriaBean should be populated as long as the field name is same as
//request parameter names.
}
I'm not sure how Spring magically achieve this(without #ModelAttribute), but the code above works for me.
There is another way to achieve the same goal, you can actually achieve more, that is spring AOP.
<bean id="aspectBean" class="au.net.test.aspect.MyAspect"></bean>
<aop:config>
<aop:aspect id="myAspect" ref="aspectBean">
<aop:pointcut id="myPointcut"
expression="execution(* au.net.test.web.*.*(..)) and args(request,bean,..)" />
<aop:before pointcut-ref="myPointcut" method="monitor" />
</aop:aspect>
</aop:config>
in application context, we declare Aspect bean as well as Pointcut along with advice, which in your case is before advice
the following is source code
public class PaginationCriteriaBean {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//custom Aspect
public class MyAspect {
public void monitor( HttpServletRequest request,PaginationCriteriaBean bean){
//populate your pagination bean
bean.setId(request.getParameter("id"));
bean.setName("my new name");
}
}
#RequestMapping(value="/app")
public String appRoot(HttpServletRequest request,PaginationCriteriaBean bean){
System.out.println(bean.getId());
System.out.println(bean.getName());
return "app";
}
by doing so, the aspect will intercept spring controller and populate PaginationCriteriaBean based on request parameters, and you can even change the original value in request. With this AOP implementation you are empowered to apply more logic against Pagination, such as logging and validation and etc.
code snippet:
#RequestMapping(method = RequestMethod.POST)//, headers = "content-type=application/x-www-form-urlencoded")
public ModelAndView create(#RequestBody UserAccountBean account) {
try{
accounts.put(account.assignId(), account);
}catch(RuntimeException ex)
{
return new ModelAndView("account/registerError");
}
return new ModelAndView("account/userVerification");
}
After receiving request, What I got is Http Status code 415:
The server refused this request because the request entity is in a format not supported by the requested resource for the requested method ().
If I change the code to this:
code snippet:
#RequestMapping(method = RequestMethod.POST,headers = "content-type=application/x-www-form-urlencoded")
public ModelAndView create(#RequestBody UserAccountBean account) {
try{
accounts.put(account.assignId(), account);
}catch(RuntimeException ex)
{
return new ModelAndView("account/registerError");
}
return new ModelAndView("account/userVerification");
}
I will get 405 Method not allowed. Funny thing is in the allow header of response, it lists GET and POST as allowed methods.
I do have a class that does JOSN mapping:
#Component
public class JacksonConversionServiceConfigurer implements BeanPostProcessor {
private final ConversionService conversionService;
#Autowired
public JacksonConversionServiceConfigurer(ConversionService conversionService) {
this.conversionService = conversionService;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AnnotationMethodHandlerAdapter) {
AnnotationMethodHandlerAdapter adapter = (AnnotationMethodHandlerAdapter) bean;
HttpMessageConverter<?>[] converters = adapter.getMessageConverters();
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJacksonHttpMessageConverter) {
MappingJacksonHttpMessageConverter jsonConverter = (MappingJacksonHttpMessageConverter) converter;
jsonConverter.setObjectMapper(new ConversionServiceAwareObjectMapper(this.conversionService));
}
}
}
return bean;
}
}
Copied from Spring examples. works great with JSON content-type.
A more general question is how to make spring mvc request handlers work with different request content-types.
Any advice would be greatly appreciated.
Unfortunately FormHttpMessageConverter (which is used for #RequestBody-annotated parameters when content type is application/x-www-form-urlencoded) cannot bind target classes (as #ModelAttribute can).
Therefore you need #ModelAttribute instead of #RequestBody. If you don't need to pass different content types to that method you can simply replace the annotation:
#RequestMapping(method = RequestMethod.POST)
public ModelAndView create(#ModelAttribute UserAccountBean account) { ... }
Otherwise I guess you can create a separate method form processing form data with the appropriate headers attribute:
#RequestMapping(method = RequestMethod.POST,
headers = "content-type=application/x-www-form-urlencoded")
public ModelAndView createFromForm(#ModelAttribute UserAccountBean account) { ... }
EDIT: Another possible option is to implement your own HttpMessageConverter by combining FormHttpMessageConverter (to convert input message to the map of parameters) and WebDataBinder (to convert map of parameters to the target object).
I was having HTTP response code of 415
My problems were resolved when I added Content Type to request header
e.g
"Content-Type: application/json"
At the heart of the problem, we wish to accept both application/json and application/x-www-form-urlencoded Content-types with the same request handler.
To do this, I use the #RequestBody, which was already working for application/json for me (and generally others from the threads I've found, but there is extra work so application/x-www-form-urlencoded can be used with #RequestBody.
First, create a new HttpMessageConverter capable of changing the request input to an object. I do this by reusing the FormHttpMessageConverter, which is already capable of changing the input to a MultiValueMap. I then change the MultiValueMap to a regular Map, and use Jackson to turn the Map to the desired object.
Here is the code for the HttpMessageConverter:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* <p>Converts HTTP requests with bodies that are application/x-www-form-urlencoded or multipart/form-data to an Object
* annotated with {#link org.springframework.web.bind.annotation.RequestBody} in the the handler method.
*
* #author Jesse Swidler
*/
public class ObjectHttpMessageConverter implements HttpMessageConverter<Object> {
private final FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
private final ObjectMapper objectMapper = new ObjectMapper();
private static final LinkedMultiValueMap<String, String> LINKED_MULTI_VALUE_MAP = new LinkedMultiValueMap<>();
private static final Class<? extends MultiValueMap<String, ?>> LINKED_MULTI_VALUE_MAP_CLASS
= (Class<? extends MultiValueMap<String, ?>>) LINKED_MULTI_VALUE_MAP.getClass();
#Override
public boolean canRead(Class clazz, MediaType mediaType) {
return objectMapper.canSerialize(clazz) && formHttpMessageConverter.canRead(MultiValueMap.class, mediaType);
}
#Override
public boolean canWrite(Class clazz, MediaType mediaType) {
return false;
}
#Override
public List<MediaType> getSupportedMediaTypes() {
return formHttpMessageConverter.getSupportedMediaTypes();
}
#Override
public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
Map<String, String> input = formHttpMessageConverter.read(LINKED_MULTI_VALUE_MAP_CLASS, inputMessage).toSingleValueMap();
return objectMapper.convertValue(input, clazz);
}
#Override
public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws UnsupportedOperationException {
throw new UnsupportedOperationException("");
}
}
There are many different ways a Spring app might pick up that message converter. For me, it was accomplished in an XML file:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.terminal.core.services.config.ObjectHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
Using #ModelAttribute is indeed the preferred way to deal with form parameters.
Using JSON worked for me as well, I suppose it makes the JSON interpreter get the data from the body.
I was trying to use PUT though, which is a bit harder.
You can read my post about it here.
Below worked for me
On server side:
#RequestMapping(value = "test", method = RequestMethod.POST, consumes = {"application/xml", "application/json"})
#ResponseStatus(HttpStatus.OK)
public #ResponseBody
String methodName(#RequestBody EntityClassName entity) {
On client side:
String json = new JSONStringer().object()
.key("key").value("value")
.endObject()
.toString();
StringEntity se = new StringEntity(json);
se.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
request.setEntity(se);
HttpResponse response = client.execute(request);
I use this code for convert html form to json .
function ConvertFormToJSON(form) {
var array = $(form).serializeArray();
var json = {};
$.each(array, function() {
json[this.name] = this.value || '';
});
return json;
}
and use single quotations was wrong . I changed ' ' to " " and problem solved.