How can I influence the process of choosing a message converter in AnnotationMethodHandlerAdapter for resulting POJO by url extension?
I would like to have more representations of one data object, while data representation should be chosen by the requested url extension e.g. /users/2.xml or /users/2.json.
Current configuration of message handlers, which should be chosen based on url extension:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
<bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"
p:marshaller-ref="xmlMarshaller" p:unmarshaller-ref="xmlMarshaller" />
</list>
</property>
</bean>
There is one way, which I'm nearly comfortable with and that is using ContentNegotiatingViewResolver, however I would like to bypass the process of view resolution and directly use message converters. Also when creating actions, using ResponseEntity in public ResponseEntity<User> showUser() provides fine grained control of resulting http status codes definitions (OK, NOT_FOUND, NO_CONTENT, ..). I couldn't find a way of using ResponseEntity with ContentNegotiatingViewResolver, which would also satisfy my needs.
Another way could be by modifying the request accept header to application/xml or application/json based on the url extension. This way, all the processing should go directly to the configured message converter. However I don't know a reasonable way to tamper the request headers.
Thanks.
Since the choose of HttpMessageConverters uses the Accept request header, perhaps the simpliest way to implement a content negotiation is to replace this header with the desired media type specified by the URL extension.
This can be implemented either as a Filter (using HttpServletRequestWrapper to replace header value) or by overriding AnnotationMethodHanlderAdapter.createHttpInputMessage() as suggested in SPR-7517 (requires Spring 3.0.2).
See also SPR-6993.
I had this same need and hacked together a servlet filter to accomplish the task. Not a work of art, but gets the job done:
public class UrlExtensionFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
if (httpServletRequest.getRequestURI().endsWith(".json")) {
MyAcceptHeaderRequest acceptHeaderRequest = new MyAcceptHeaderRequest(httpServletRequest);
acceptHeaderRequest.setAcceptHeader("application/json");
filterChain.doFilter(acceptHeaderRequest, response);
} else if (httpServletRequest.getRequestURI().endsWith(".xml")) {
MyAcceptHeaderRequest acceptHeaderRequest = new MyAcceptHeaderRequest(httpServletRequest);
acceptHeaderRequest.setAcceptHeader("text/xml");
filterChain.doFilter(acceptHeaderRequest, response);
} else {
filterChain.doFilter(request, response);
}
}
public void destroy() {
}
public class MyAcceptHeaderRequest extends HttpServletRequestWrapper {
private String accept = "application/json";
public MyAcceptHeaderRequest(HttpServletRequest request) throws IOException {
super(request);
}
public void setAcceptHeader(String value) {
accept = value;
}
#Override
public String getHeader(String name) {
if (name.equalsIgnoreCase("accept") || name.equalsIgnoreCase("content-type")) {
return accept;
} else {
return super.getHeader(name);
}
}
#Override
public Enumeration getHeaders(String name) {
if (name.equalsIgnoreCase("accept") || name.equalsIgnoreCase("content-type")) {
Enumeration enumeration = new StringTokenizer(accept);
return enumeration;
} else {
return super.getHeaders(name);
}
}
#Override
public String getContentType() {
return accept;
}
#Override
public String getParameter(String name) {
// When we're using this class and it is a POST operation then the body is JSON or XML so don't allow
// attempts to retrieve parameter names to consume the input stream
if (this.getMethod().equals("POST")) {
return null;
} else {
return super.getParameter(name);
}
}
#Override
public String[] getParameterValues(String name) {
// When we're using this class and it is a POST operation then the body is JSON or XML so don't allow
// attempts to retrieve parameter names to consume the input stream
if (this.getMethod().equals("POST")) {
return null;
} else {
return super.getParameterValues(name);
}
}
}
}
Related
I was get Controller's #PathVariable in Pre-Handler Interceptor.
Map<String, String> pathVariable = (Map<String, String>) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE );
But I wish to modify #PathVariable value(below).
#RequestMapping(value = "{uuid}/attributes", method = RequestMethod.POST)
public ResponseEntity<?> addAttribute(#PathVariable("uuid") String uuid, HttpServletRequest request, HttpServletResponse response) {
//LOGIC
}
How to modify #PathVariable("uuid") value in interceptor before going to controller??
I'm using Spring 4.1 and JDK 1.6. I can't upgrade its.
A general use for interceptors is to apply generic functionality to controllers. I.e. default data shown on all pages, security etc. You want to use it for a single piece of functionality which you generallly shouldn't do.
What are you trying to achieve isn't possible with an interceptor. As first the method to execute is detected based on the mapping data. Before executing the method the interceptor is executed. In this you basically want to change the incoming request and to execute a different method. But the method is already selected, hence it won't work.
As you eventually want to call the same method simply add another request handling method which either eventually calls addAttribute or simply redirects to the URL with the UUID.
#RequestMapping("<your-url>")
public ResponseEntity<?> addAttributeAlternate(#RequestParam("secret") String secret, HttpServletRequest request, HttpServletResponse response) {
String uuid = // determine UUID based on request
return this.addAttribute(uuid,request,response);
}
Try below given code.
public class UrlOverriderInterceptor implements ClientHttpRequestInterceptor {
private final String urlBase;
public UrlOverriderInterceptor(String urlBase) {
this.urlBase = urlBase;
}
private static Logger LOGGER = AppLoggerFactory.getLogger(UrlOverriderInterceptor.class);
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
URI uri = request.getURI();
LOGGER.warn("overriding {0}", uri);
return execution.execute(new MyHttpRequestWrapper(request), body);
}
private class MyHttpRequestWrapper extends HttpRequestWrapper {
public MyHttpRequestWrapper(HttpRequest request) {
super(request);
}
#Override
public URI getURI() {
try {
return new URI(UrlUtils.composeUrl(urlBase, super.getURI().toString())); //change accordingly
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
}
I have the following declaration:
#RequestMapping(value= "/", method = RequestMethod.POST, headers = "Accept=application/vnd.woot4moo-v1+json"
public #ResonseBody void create(String key){...}
This of course will take json but I want it to reject any request that does not contain the vnd.woot4moo-v1 part of the header. Is this even feasible?
You can filter the specific requests by implementing HandlerInterceptor as below:
RequestMethodInterceptor class:
public class RequestMethodInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//Get the 'Accept' header value
String headerValue = request.getHeader("Accept");
//check the request contains expected header value
if(!headerValue.equals("application/vnd.woot4moo-v1+json")) {
//Reject and Log or Ignore upon your requirement & return false
return false;
} else {
return true;
}
}
}
XML Configuration:
<mvc:interceptors>
<bean class="xyz.RequestMethodInterceptor" />
</mvc:interceptors>
I'm using Java 8, Tomcat 8, Spring-WebMVC 4.2.2.RELEASE, FasterXML 2.6.3.
I have the following method in my controller
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public void updateCurrentUserDetails(#RequestBody final UserDTO userDTO) {
final UserWithId user = SecurityUtil.getCurrentUser();
this.userAccountService.updateUserDetails(user.getUserId(), user.getUsername(), userDTO);
}
This method returns void which resolves in an empty (0 byte) response. However the clients connecting to the server always expect JSON reponses even, if its an empty response.
So I would like to configure Spring/Jackson to return {} (2 byte) in that case.
I already thought about returning new Object() everywhere in the calls that would return void otherwise but IMO this is a dirty soution and there must be something better.
There shouldn't be any need to do all that. You can just use a 204 response code, which is made for the situation you are describing. You don't even need the ResponseBody annotation, just use:
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.NO_CONTENT)
public void updateCurrentUserDetails(#RequestBody final UserDTO userDTO) {
final UserWithId user = SecurityUtil.getCurrentUser();
this.userAccountService.updateUserDetails(user.getUserId(), user.getUsername(), userDTO);
}
204 response code:
The 204 (No Content) status code indicates that the server has
successfully fulfilled the request and that there is no additional
content to send in the response payload body.
Its quite easy.
Just add the following to your spring xml/java config
<mvc:interceptors>
<bean class="de.st_ddt.util.VoidResponseHandlerInterceptor" />
</mvc:interceptors>
And add this class to your classpath
public class VoidResponseHandlerInterceptor extends HandlerInterceptorAdapter {
private static final String voidResponse = "{}";
#Override
public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler,
final ModelAndView modelAndView) throws IOException {
// Returned void?
if (!response.isCommitted()) {
// Used ModelAndView?
if (modelAndView != null) {
return;
}
// Access static resource?
if (DefaultServletHttpRequestHandler.class == handler.getClass()) {
return;
}
response.setStatus(200);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
try (final Writer writer = response.getWriter()) {
writer.write(voidResponse);
}
response.flushBuffer();
}
}
}
I've built a json REST service with controllers like this one:
#Controller
#RequestMapping(value = "/scripts")
public class ScriptController {
#Autowired
private ScriptService scriptService;
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public List<Script> get() {
return scriptService.getScripts();
}
}
It works fine, but now I need to modify all responses and add "status" and "message" fields to all of them. I've read about some solutions:
return from all controller methods object of some specific class, for example,
RestResponse, which will contain "status" and "message" fields (but it's
not general solution, cause I will have to modify all my controllers
and write new controllers in new style)
intercept all controller methods with aspects (but in this case I can't change return type)
Can you suggest some other, general and correct solution, if I want to wrap values returned from controller methods into objects of class:
public class RestResponse {
private int status;
private String message;
private Object data;
public RestResponse(int status, String message, Object data) {
this.status = status;
this.message = message;
this.data = data;
}
//getters and setters
}
I've encountered with similar problem and suggest you to use Servlet Filters to resolve it.
Servlet Filters are Java classes that can be used in Servlet Programming to intercept requests from a client before they access a resource at back end or to manipulate responses from server before they are sent back to the client.
Your filter must implement the javax.servlet.Filter interface and override three methods:
public void doFilter (ServletRequest, ServletResponse, FilterChain)
This method is called every time a request/response pair is passed through the chain due to a client request for a resource at the end of the chain.
public void init(FilterConfig filterConfig)
Called before the filter goes into service, and sets the filter's configuration object.
public void destroy()
Called after the filter has been taken out of service.
There is possibility to use any number of filters, and the order of execution will be the same as the order in which they are defined in the web.xml.
web.xml:
...
<filter>
<filter-name>restResponseFilter</filter-name>
<filter-class>
com.package.filters.ResponseFilter
</filter-class>
</filter>
<filter>
<filter-name>anotherFilter</filter-name>
<filter-class>
com.package.filters.AnotherFilter
</filter-class>
</filter>
...
So, this filter gets the controller response, converts it into String, adds as feild to your RestResponse class object (with status and message fields), serializes it object into Json and sends the complete response to the client.
ResponseFilter class:
public final class ResponseFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) response);
chain.doFilter(request, responseWrapper);
String responseContent = new String(responseWrapper.getDataStream());
RestResponse fullResponse = new RestResponse(/*status*/, /*message*/,responseContent);
byte[] responseToSend = restResponseBytes(fullResponse);
response.getOutputStream().write(responseToSend);
}
#Override
public void destroy() {
}
private byte[] restResponseBytes(RestResponse response) throws IOException {
String serialized = new ObjectMapper().writeValueAsString(response);
return serialized.getBytes();
}
}
chain.doFilter(request, responseWrapper) method invokes the next filter in the chain, or if the calling filter is the last filter in the chain invokes servlet logic.
The HTTP servlet response wrapper uses a custom servlet output stream that lets the wrapper manipulate the response data after the servlet is finished writing it out. Normally, this cannot be done after the servlet output stream has been closed (essentially, after the servlet has committed it). That is the reason for implementing a filter-specific extension to the ServletOutputStream class.
FilterServletOutputStream class:
public class FilterServletOutputStream extends ServletOutputStream {
DataOutputStream output;
public FilterServletOutputStream(OutputStream output) {
this.output = new DataOutputStream(output);
}
#Override
public void write(int arg0) throws IOException {
output.write(arg0);
}
#Override
public void write(byte[] arg0, int arg1, int arg2) throws IOException {
output.write(arg0, arg1, arg2);
}
#Override
public void write(byte[] arg0) throws IOException {
output.write(arg0);
}
}
To use the FilterServletOutputStream class should be implemented a class that can act as a response object. This wrapper object is sent back to the client in place of the original response generated by the servlet.
ResponseWrapper class:
public class ResponseWrapper extends HttpServletResponseWrapper {
ByteArrayOutputStream output;
FilterServletOutputStream filterOutput;
HttpResponseStatus status = HttpResponseStatus.OK;
public ResponseWrapper(HttpServletResponse response) {
super(response);
output = new ByteArrayOutputStream();
}
#Override
public ServletOutputStream getOutputStream() throws IOException {
if (filterOutput == null) {
filterOutput = new FilterServletOutputStream(output);
}
return filterOutput;
}
public byte[] getDataStream() {
return output.toByteArray();
}
}
I think this approach will be a good solution for your issue.
Please, ask a questions, if something not clear and correct me if I'm wrong.
If you use spring 4.1 or above, you can use ResponseBodyAdvice to customizing
response before the body is written.
I ran into a problem when trying to port our old ERX rest routes to Jerey/JAX-RX.
I am trying to do something like this:
#Path("/v0/user")
#Controller
public class UserRouteController {
#GET
public Response getAllUsers(){
...
}
#GET
#Path("/{name}")
public Response getUserWithName(#PathParam("name") String name){
...
}
#GET
#Path(":accessibleWithNoRestriction")
public Response getUsersAccessibleWithNoRestriction(){
...
}
#GET
#Path(":withAdminStatus")
public Response getUsersWithAdminStatus(){
...
}
However, Jersey does not want to match my http request.
blahblah.com/v0/user:accessibleWithNoRestriction
I get a No Method Allowed response.
I normally don't include the leading/trailing /'s in my paths, so I'm pretty sure #Path("rootLevel") and #Path("methodLevel") actually maps to rootLevel/methodLevel not rootLevelMethodLevel.
So in your example, #Path("/v0/user") and #Path(":withAdminStatus") maps to /v0/user/:withAdminStatus. Try changing your paths to something like this:
#Path("v0")
#Controller
public class UserRouteController {
#GET
#Path("user")
public Response getAllUsers(){
//...
}
#GET
#Path("user/{name}")
public Response getUserWithName(#PathParam("name") String name){
//...
}
#GET
#Path("user:accessibleWithNoRestriction")
public Response getUsersAccessibleWithNoRestriction(){
//...
}
#GET
#Path("user:withAdminStatus")
public Response getUsersWithAdminStatus(){
//...
}
}
Alternatively, you might be able to pull something off with some kind of redirection. For example, with a Pre-matching Filter. I've never done anything like this, but the documentation suggests that "you can even modify request URI". With that in mind, you could replace any request with :withAdminStatus in the URI with /:withAdminStatus so that it can be matched with the correct resource.
I came across this post: JAX-RS Application on the root context - how can it be done?
Try using this:
#WebFilter(urlPatterns = "/*")
public class PathingFilter implements Filter {
Pattern[] restPatterns = new Pattern[] {
Pattern.compile("/v0/user:.*")
};
#Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
String path = ((HttpServletRequest) request).getPathInfo();
for (Pattern pattern : restPatterns) {
if (pattern.matcher(path).matches()) {
String[] segments = path.split(":");
String newPath = segments[0] + "/" + segments[1];
newPath = ((HttpServletRequest) request).getServletPath() + "/" + newPath;
request.getRequestDispatcher(newPath).forward(request, response);
return;
}
}
}
chain.doFilter(request, response);
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
}
Then you'll have to change the #Path annotation in your method to "/accessibleWithNoRestriction"
What this would do is change the uri of your request before the matching happens.
Try that