Jersey, ContainerRequestFilters and DELETE - java

I'm using Jersey 1.11 and trying to model both POST and DELETE methods in my RESTful resource.
The problem I am encountering is in my unit tests. I cannot use the delete method of WebResource and still expect a ContentResponse instance (there are workarounds but since I will at some point use a Rails front end I'd rather sort this out now). So I'm trying to use POST with the PostReplaceFilter and have variously tried submitting form and query _method parameters (set to DELETE) as well as the X-HTTP-Method-Override header.
I am configuring the PostReplaceFilter in web.xml as such:
<servlet-name>SomeName</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
<param-value>com.sun.jersey.api.container.filter.PostReplaceFilter</param-value>
</init-param>
My REST resource looks like this:
#Controller
#Scope("prototype")
#Path("{apiVersion}/push/")
public class Push extends BaseRequest {
#POST
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
#Produces(MediaType.APPLICATION_JSON)
#Path("registration")
public Response create(#FormParam(REGISTRATION_ID_FIELD) String registrationId) throws Exception {
/* omitted; this method ends in a 200 OK */
}
#DELETE
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
#Path("registration")
public Response destroy(#FormParam(REGISTRATION_ID_FIELD) String registrationId) throws Exception {
/* omitted; this method ends in a 204 NO CONTENT */
}
My unit test always invokes the first create method as if the PostReplaceFilter is having no effect:
private WebResource.Builder prepare(String path, String method) {
return resource().path(path)
.queryParam("_method", method)
.cookie(new Cookie(Report.DEVICE_COOKIE_NAME, TEST_DEVICE_ID))
.cookie(new Cookie(Report.TOKEN_COOKIE_NAME, TEST_TOKEN));
}
#Test
public void destroyShouldEliminateAnyPushRegistrationAndTokenForADevice() throws Exception {
// mocks are setup here
MultivaluedMap<String, String> formData = createFormData(registrationId);
ClientResponse response = prepare("/1.0/push/registration", "DELETE")
.header("X-HTTP-Method-Override", "DELETE")
.post(ClientResponse.class, formData);
assertThat(response.getStatus(), is(204));
// mocks are verified here
}
I know the first method is being invoked because create returns a 200 while the destroy method returns 204 and the assertThat line is failing due to a 200 status (additionally my expected methods on my mocks are not being invoked).
My unit test inherits from JerseyTest and uses the Grizzly2 Web Container.

My co-worker pointed out that the web.xml is not actually used as part of the test stack. Instead I needed to initialize the filters programmatically; in our case our base test inherited from JerseyTest and overwrote the configure method:
#Override
protected AppDescriptor configure() {
return new WebAppDescriptor.Builder("com.yourproject.blah")
.initParam("com.sun.jersey.spi.container.ContainerRequestFilters",
"com.sun.jersey.api.container.filter.PostReplaceFilter")
.servletClass(SpringServlet.class)
.build();
}

Related

response.sendRedirect shows unwanted HttpStatus 302 instead of 307

I have a small test, which should return a HttpStatus with Temporary Redirect with HttpStatus Code 307.
But it always returns a 302.
#RestController
#RequestMapping(value = "/")
public class Controller {
#ResponseStatus(HttpStatus.TEMPORARY_REDIRECT )
#RequestMapping(value= "test", method = RequestMethod.GET)
public void resolveUrl(HttpServletResponse response) throws IOException {
response.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT);
response.sendRedirect("https://www.google.com");
}
}
When I look into the documentation of response.sendRedirect() I can read this:
Sends a temporary redirect response to the client using the specified
* redirect location URL.
and the documentation of temporary redirect is a 307:
10.3.8 307 Temporary Redirect
The requested resource resides temporarily under a different URI.
Since the redirection MAY be altered on occasion, the client SHOULD
continue to use the Request-URI for future requests. This response is
only cacheable if indicated by a Cache-Control or Expires header
field.
(I know, that I don't need the #ResponseStatus(HttpStatus.TEMPORARY_REDIRECT) or the response.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT); but I want to show that it will not work with this things too!)
But my test shows that it was a 302 and not a 307
java.lang.AssertionError: Status expected:<307> but was:<302>
Can somebody explain this?
My small test for this:
#RunWith(SpringRunner.class)
#WebMvcTest(Controller.class)
public class ControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void test() throws Exception {
MockHttpServletRequestBuilder requestBuilder = get("/test");
mvc.perform(requestBuilder).andExpect(status().isTemporaryRedirect())
.andExpect(redirectedUrl("https://www.google.com"));
}
}
Complete code can be found at github
Instead using sendRediect , set Location header in response object.
response.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT);
response.setHeader("Location","https://we.google.com");

Intercepting based on HTTP header in RESTeasy

I am developing REST services with two types.
before login no session token will be passed to HTTP header.
after login session token will be passed in each request.
I dont want to include #HeaderParam in each and every REST method. I want to intercept it first and based on that I want to check the validity of session. Please let me know
how I can intercept based on headers in RESTEasy
How to avoid intercepting few methods
Thanks.
I solved this problem using PreProcessInterceptor
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Securable {
String header() default "session-token";
}
#Provider
#ServerInterceptor
public class ValidationInterceptor implements PreProcessInterceptor, AcceptedByMethod {
#Context
private HttpServletRequest servletRequest;
#Override
public boolean accept(Class clazz, Method method) {
return method.isAnnotationPresent(Securable.class);
}
#Override
public ServerResponse preProcess(HttpRequest httpRequest, ResourceMethod resourceMethod) throws Failure,
WebApplicationException {
Securable securable = resourceMethod.getMethod().getAnnotation(Securable.class);
String headerValue = servletRequest.getHeader(securable.header());
if (headerValue == null){
return (ServerResponse)Response.status(Status.BAD_REQUEST).entity("Invalid Session").build();
}else{
// Validatation logic goes here
}
return null;
}
}
The annotation #Securable will be used on REST service which needs to be validated.
#Securable
#PUT
public Response updateUser(User user)
There are two approaches
Use JAX-RS interceptors - you have access to request object in the interceptor, so you can read headers
Use good old JavaServlet Filters - it is not a problem that you are using JAX-RS, you can filter REST requests as well. Similarly to interceptors, filters have access to request object, which has header information
In both cases you can check if HttpSession exists (request.getSession() method) and it has required attribute.
You can include/exclude requests filtered either in configuration or programatically in Java code, looking at request path.

Jersey Test Framework - define default error response for all unknown paths in grizzly

To test our API that connects to the facebook graph API we use a mock server setup based on Jersey Test Framework and grizzly:
#Path("/" + PostRest.RESOURCE)
#Produces("application/json")
public class PostRest {
public static final String RESOURCE = "111_222";
#GET
public Response getPost(#QueryParam("access_token") String access_token) {
if (access_token != VALID_TOKEN) {
return Response.status(400).entity(createErrorJson()).build();
}
return Response.status(200).entity(createSomeJsonString()).build();
}
Now while I can react to an invalid or missing access_token with the correct error response, I also want to test that my API reacts correctly when trying to access an unkown resource at facebook ie an unkown path.
Right now I get a 404 from my grizzly obviously, if I try to access say "/111_2", but facebook seems to catch that error and wrap it inside a Json response, containing the string "false" with status 200.
So... How do I set up the Test Framework to return
Response.status(200).entity("false").build();
every time it is called for an known path?
Basic example:
#ContextConfiguration({ "classpath:context-test.xml" })
#RunWith(SpringJUnit4ClassRunner.class)
public class SomeTest extends JerseyTest {
#Inject
private SomeConnection connection;
private String unkownId = "something";
public SomeTest() throws Exception {
super("jsonp", "", "com.packagename.something");
}
#Test(expected = NotFoundException.class)
public void testUnkownObjectResponse() throws NotFoundException {
// here it should NOT result in a 404 but a JSON wrapped error response
// which will be handled by the Connection class and
// result in a custom exception
connection.getObject(unkownId);
}
Or maybe I can set up grizzly to behave as desired..?!
Thanks!
Obviously facebook has it own service to intercept errors. Same thing should be done in your code. Just expose you own test service that intercepts all request
#Path("/test/errorTrap")
public class ErrorTrapService{
....
}
This service will produce any response you want. So any un-existing pages like http://mytest/test/errorTrap/111_2 will be intercepted by test service and produce expected response for you

How can I override the decisions made during JAX-RS Content Negotiation?

I'm using RESTEasy 2.2.1.GA as my JAX-RS implementation to create a client to connect to a third party service provider. (Education.com's REST API if it matters)
To make sure I haven't missed an important implementation detail here are code samples:
Service Interface
#Path("/")
public interface SchoolSearch {
#GET
#Produces({MediaType.APPLICATION_XML})
Collection<SchoolType> getSchoolsByZipCode(#QueryParam("postalcode") int postalCode);
}
Calling Class
public class SimpleSchoolSearch {
public static final String SITE_URL = "http://api.education.com/service/service.php?f=schoolSearch&key=****&sn=sf&v=4";
SchoolSearch service = ProxyFactory.create(SchoolSearch.class, SITE_URL);
public Collection<SchoolType> getSchools() throws Exception {
Collection<SchoolType> schools = new ArrayList<SchoolType>();
Collection<SchoolType> response = service.getSchoolsByZipCode(35803);
schools.addAll(response);
return schools;
}
}
After setting up tests to make this call, I execute and see the following exception being thrown.
org.jboss.resteasy.plugins.providers.jaxb.JAXBUnmarshalException: Unable to find JAXBContext for media type: text/html;charset="UTF-8"
From reading the RESTEasy/JAX-RS documentation, as I understand it, when the response is returned to the client, prior to the unmarshaling of the data, a determination is made (Content Negotiation??) about which mechanism to use for unmarshalling. (I think we're talking about a MessageBodyReader here but I'm unsure.) From looking at the body of the response, I see that what is returned is properly formatted XML, but the content negotiation (via HTTP header content-type is indeed text/html;charset ="UTF-8") is not allowing the text to be parsed by JAXB.
I think that the implementation is behaving correctly, and it is the service that is in error, however, I don't control the service, but would still like to consume it.
So that being said:
Am I correct in my understanding of why the exception is thrown?
How do I work around it?
Is there a simple one line annotation that can force JAXB to unmarshal the data, or will I need to implement a custom MessageBodyReader? (If that is even the correct class to implement).
Thanks!
Follow Up:
I just wanted to post the few changes I made to Eiden's answer. I created a ClientExecutionInterceptor using his code and the information available at Resteasy ClientExecutionInterceptor documentation. My final class looks like
#Provider
#ClientInterceptor
public class SimpleInterceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse response = ctx.proceed();
response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
The big difference is the addition of the #Provider and #ClientExecutionInterceptor annotations. This should insure that the interceptor is properly registered.
Also, just for completeness, I registered the Interceptor slightly differently for my tests. I used:
providerFactory.registerProvider(SimpleInterceptor.class);
I'm sure there are several solutions to this problem, but I can only think of one.
Try so set the content-type using a ClientExecutionInterceptor:
public class Interceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse<?> execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse<?> response = ctx.proceed();
response
.getHeaders()
.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
public void getSchools() throws Exception {
ResteasyProviderFactory.getInstance()
.getClientExecutionInterceptorRegistry()
.register( new Interceptor() );
SchoolSearch service =
ProxyFactory.create(SchoolSearch.class, SITE_URL);
}
I dont know about any such annotation, others might do, but a workaround is to create a local proxy. Create a controller, that passes all parameters to education.com using a
java.Net.URL.get()
return the answer that you received, but modify the header. Then connect your client to the local proxy controller.

Changing content type in jax-rs REST service

Forgive me, but I may not be familiar with all the lingo necessary to ask this question properly.
I'm working on a fairly simple REST web service in Java using the org.apache.cxf.jaxrs.ext implementation of jax-rs. The method header is like this:
#GET
#Path("json/{fullAlias}")
#Produces({"application/json"})
public String json(#PathParam("fullAlias") String fullAlias, #Context MessageContext req)
where MessageContext is org.apache.cxf.jaxrs.ext.MessageContext.
There are two things I'm trying to accomplish that I can't seem to figure out:
Change the content-type if certain conditions are met (e.g. for an error)
Change the status code of the response
I've tried using changing the response by accessing it through the MessageContext:
HttpServletResponse response = req.getHttpServletResponse();
response.setContentType("text/plain")
response.setStatus("HttpServletResponse.SC_BAD_REQUEST);
But these changes have no bearing on the response sent; with or without the #Produces annotation, setting the content type inside the method doesn't affect the actual content type (With the annotation, it of course returns "application/json", without it defaults to "text/html").
I am returning a simple String as the body. I've entertained trying to return a javax.ws.rs.core.Response object to do what I want, but I don't know much about it.
How would I change the content type and/or the status codes from inside this method?
One approach is to throw a WebApplicationException, as described by Pace, which will work if you are looking to specifically handle an error condition. If you are looking to be able to change your content at any time for any reason, then you will want to take a look at returning a Response as the result of your service method rather than a String. Returning a Response gives you the greatest amount of control over how your service responds to the client request (it does require more code than returning a simple string).
Here is an example of how you would can make use of the Response object:
#GET
#Path("json/{fullAlias}")
public Response json(#PathParam("fullAlias") String fullAlias, #Context MessageContext req) {
...
if (success) {
ResponseBuilder rBuild = Response.ok(responseData, MediaType.APPLICATION_JSON);
return rBuild.build();
}
else {
ResponseBuilder rBuild = Response.status(Response.Status.BAD_REQUEST);
return rBuild.type(MediaType.TEXT_PLAIN)
.entity("error message")
.build();
}
}
I'm not sure if it's the best approach but I've done the following to solve your question #1.
public WebApplicationException createStatusException(String statusMessage) {
ResponseBuilder rb = Response.noContent();
rb = rb.type(MediaType.TEXT_PLAIN);
rb = rb.status(Status.BAD_REQUEST);
rb = rb.entity(statusMessage);
return new WebApplicationException(rb.build());
}
EDIT: I then threw the resulting WebApplicationException.
You can write your own Response Filter to change the content-type header.
#Provider
public class MimeAddingFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("Content-Type", "image/png");
}
}
This filter will add the "image/png" content-type header. You can also change or remove headers in JAX-RS response filters.

Categories

Resources