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
Related
I have a Java project and I'm using Servlet in order to handle http requests.
I also using Spring
When I receive a request to create a new object (for example an account), I would like also to return the “location” header with the GET URL of the newly created object.
for example: location: /accounts/1000
I understand the header are added to the Servlet filter (correct me if Im wrong)
public class ApiLogFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger("apilogger");
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = ((HttpServletResponse) servletResponse);
httpServletResponse.addHeader( "Location","the location value");
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
String queryString = httpServletRequest.getQueryString() != null ? httpServletRequest.getQueryString() : "N/A";
String logMessage = "URL: " + httpServletRequest.getRequestURL() + ", Query String: " + queryString + ", Response Status: " + httpServletResponse.getStatus() ;
LOGGER.info(logMessage);
}
}
#Override
public void destroy() {
}
}
But I don't understand how to get the location value from the API
#RequestMapping("/accounts")
public class IgnoreRuleController {
private AccountService accountService;
public void setIgnoreRuleService(IgnoreRuleService ignoreRuleService) {
this.accountService = ignoreRuleService;
}
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public String createAccount(#RequestBody Account account) {
return new Gson().toJson(accountService.createAccount(account));
}
}
I found solution here
http://learningviacode.blogspot.com/2013/07/post-with-location.html
you didn't need to do anything with the filter.
in the api itself:
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<String> createIgnoreRule(#RequestBody IgnoreRule ignoreRule) {
String response = new Gson().toJson(ignoreRuleService.createIgnoreRule(ignoreRule));
final URI location = ServletUriComponentsBuilder
.fromCurrentServletMapping().path("/ignore_rules/{id}").build()
.expand(ignoreRule.getId()).toUri();
final HttpHeaders headers = new HttpHeaders();
headers.setLocation(location);
final ResponseEntity<String> entity = new ResponseEntity<>(response, headers, HttpStatus.CREATED);
return entity;
}
It's very simple, you can pass the header directly throw your method signature:
#RequestMapping(value="/create-account", method = RequestMethod.POST)
#ResponseBody
public String createAccount(#RequestHeader HttpHeaders httpHeader, #RequestBody Account account) {
var s = httpHeader.get("Location");
System.out.println(s.get(0));
return ...
}
In fact you can pass the whole request also which contains everything (Headers, Body, ...):
#RequestMapping(value="/create-account", method = RequestMethod.POST)
#ResponseBody
public String createAccount(HttpServletRequest httpRequest, #RequestBody Account account) {
var s = httpRequest.getHeader("Location");
System.out.println(s);
return ....
}
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'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 have a filter in one of the jar files added as reference.
My project is in spring nature. I have developed a web services. All the requests to this web services should be intercepted by my filter "HelloWorld".
This filter is there in one of the reference files.
Here I thought I would implement it as Interceptor.
The filter in reference files looks like
public class HelloWorld implements Filter {
private static final Logger _logger = Logger.getLogger(HelloWorld.class.getName());
protected String name = null;
public HelloWorld()
{
}
public HelloWorld(String name) {
this.name = name;
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
PrintWriter out = response.getWriter();
out.println("Hello "+name);
chain.doFilter(req, res);
}
public void init(FilterConfig config) throws ServletException {
//Get init parameter
String testParam = config.getInitParameter("test");
//Print the init parameter
System.out.println("test param: " + testParam);
}
public void destroy() {
//add code to release any resource
}
//some other methods as well
}
What would be the best way to implement this. I cannot configure filter in my web.xml due to limitation in my application.
Can we directly give this HelloWorld filter as reference to interceptor, so that it behaves like interceptor.
Can we change this filter as interceptor in spring and configure in spring.xml without changing the functionality.
Apologies if my question is simple.
Thanks.
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);
}
}
}
}