1

I am currently working on an Android app which uses Resteasy-mobile for the REST(JAXRS) implementation, and Jackson for serialization to and from JSON. The latter is done by Resteasy in the background.

Serverside I have a Jersey webservice, also using jackson for serialization to/from JSON.

Both the Resteasy client, and the webresource implement a JAXRS annotated interface like this:

    @Path("/equipment")
    @Consumes({"application/json"})
    @Produces({"application/json"})
    public interface AndroidEquipmentResourceIF
    {
      @GET 
      public Model getModel();

      @GET 
      @Path("/version") 
      public String getVersion();

      @GET 
      @Path("/{eId}") 
      public List<Equipment> getEquipmentListWithId(@PathParam("eId") String eId);
    }

Clientside, this interface is loaded into a factory, and returns an object with this interface, which deals with the HTTP/URL/serialization process when a method is called.

When an Equipment entity with a invalid ID is called, the server throws a WebApplicationException if the entity was not found. Clientside, this probably causes the method to return null.

If i'd like to do something with the status code, like 404 - Not found or 401 - Unauthorized, I probably need the response object right? I think Resteasy and Jersey do work with Response objects in the background, so is there a way to access the Response object, or status code, with Resteasy?

EDIT1: Forgot to mention the second option. I noticed it is possible to wrap an object in a response object. So if i'd replace all the specific domain object return types to Response, and wrap the object in the response somehow, i could extract the object from the Response object? This does not sound very clean to me, just for being able to see the status code. Also I'd like to keep away from manual parsing of JSON as far as possible.

EDIT2: Found out it is possible to intercept the WebApplicationException, the Resteasy client throws a ClientResponseFailure exception, which can be caught in a interceptor. Problem is, the exception is never thrown? It looks like it gets stuck in a infenite loop somewhere.

I am getting the following exception:

10-26 10:52:10.048: E/AndroidRuntime(282): Caused by: java.lang.StackOverflowError
10-26 10:52:10.048: E/AndroidRuntime(282):  at java.util.regex.Matcher.reset(Matcher.java:151)
10-26 10:52:10.048: E/AndroidRuntime(282):  at java.util.regex.Matcher.reset(Matcher.java:211)
10-26 10:52:10.048: E/AndroidRuntime(282):  at java.util.regex.Matcher.<init>(Matcher.java:127)
10-26 10:52:10.048: E/AndroidRuntime(282):  at java.util.regex.Pattern.split(Pattern.java:405)
10-26 10:52:10.048: E/AndroidRuntime(282):  at java.util.regex.Pattern.split(Pattern.java:355)
10-26 10:52:10.048: E/AndroidRuntime(282):  at java.lang.String.split(String.java:2125)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.plugins.delegates.MediaTypeHeaderDelegate.parse(MediaTypeHeaderDelegate.java:33)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.plugins.delegates.MediaTypeHeaderDelegate.fromString(MediaTypeHeaderDelegate.java:18)
10-26 10:52:10.048: E/AndroidRuntime(282):  at javax.ws.rs.core.MediaType.valueOf(MediaType.java:173)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.getMediaType(BaseClientResponse.java:362)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.getEntity(BaseClientResponse.java:346)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.getEntity(BaseClientResponse.java:319)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.getEntity(BaseClientResponse.java:442)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.copyFromError(BaseClientResponse.java:94)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.ClientResponseFailure.<init>(ClientResponseFailure.java:32)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.createResponseFailure(BaseClientResponse.java:488)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.createResponseFailure(BaseClientResponse.java:479)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.readFrom(BaseClientResponse.java:384)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.getEntity(BaseClientResponse.java:346)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.getEntity(BaseClientResponse.java:319)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.getEntity(BaseClientResponse.java:442)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.copyFromError(BaseClientResponse.java:94)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.ClientResponseFailure.<init>(ClientResponseFailure.java:32)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.createResponseFailure(BaseClientResponse.java:488)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.createResponseFailure(BaseClientResponse.java:479)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.readFrom(BaseClientResponse.java:384)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.getEntity(BaseClientResponse.java:346)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.getEntity(BaseClientResponse.java:319)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.getEntity(BaseClientResponse.java:442)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.core.BaseClientResponse.copyFromError(BaseClientResponse.java:94)
10-26 10:52:10.048: E/AndroidRuntime(282):  at org.jboss.resteasy.client.ClientResponseFailure.<init>(ClientResponseFailure.java:32)
NickL
  • 4,258
  • 2
  • 21
  • 38
  • This might help [http://stackoverflow.com/questions/2022007/jax-jersey-custom-error-code-in-response][1] [1]: http://stackoverflow.com/questions/2022007/jax-jersey-custom-error-code-in-response – user18428 Oct 26 '12 at 08:18
  • Not really. Those questions are related to adding a custom error message to the Response object, which is not what I am using. I am returning a Object, which is not a Response. – NickL Oct 26 '12 at 08:25
  • 1
    Throwing a custom exception (inheriting from WebApplicationException) from the server method if the id is invalid is not an option for you? – user18428 Oct 26 '12 at 08:28
  • That is exactly what I am doing now, but I don't think I can catch the exception client side? Or can I? – NickL Oct 26 '12 at 08:29
  • 1
    I don't know reasteasy-mobile but you should try. I think you'll get an exception client side. – user18428 Oct 26 '12 at 08:36
  • Hmm indeed something happens, I think the client is trying to throw a ClientResponseFailure exception, but it then enters a (looks like) infenite loop.. which ends with a StackOverflowError. Might be a bug in Resteasy-mobile? Its a shame this client is not supported by JBOSS. It is a pruned version of the 2.2.1.GA resteasy client. – NickL Oct 26 '12 at 08:58

1 Answers1

2

Got it working the following way:

Resteasy-mobile is a pruned version of JBOSS Resteasy 2.2.1 GA. A lot of things have been removed to make it work on Android. But a bit too much is removed.

I found out that if a ClientResponseFailure is thrown, the Response its entity is cast to a Byte array, and will be read again for copying to the ClientResponseFailure. Problem is, the pruned version only contains Messagebody reader/writers for Strings and text/plain, not for byte arrays. So when trying to read the entity, it tries to throw a new ClientResponseFailure for not being able to find a MessageBodyReader that is compatible with Byte[]. It then casts this entity to a byte array again, tries to read it again, thus entering a infinite loop which will end with a StackOverflowError.

By adding the missing ByteArrayProvider and ReadFromStream class from the non-pruned version of Resteasy to classpath(at org.jboss.resteasy.plugins.providers.ByteArrayProvider and org.jboss.resteasy.util.ReadFromStream), the ClientErrorInterceptor (link) works like a charm!

EDIT: I spoke a bit too soon, didn't work like a charm yet. The ResteasyProviderFactory has also been modified to get it working in Android. The .getInstance() method keeps returning a new instance, so if you register a ClientErrorInterceptor with: ResteasyProviderFactory.getInstance().addClientErrorInterceptor(new DataExceptionInterceptor()); It does not work. The ProxyFactory will use a new instance of the ResteasyProviderFactory internaly.

To work around this problem, the ResteasyProviderFactory used for adding the interceptor, should be passed to the ProxyFactory.create().

Like this:

ResteasyProviderFactory pf;
BasicHttpParams params;

pf = ResteasyProviderFactory.getInstance();

pf.addClientErrorInterceptor(new DataExceptionInterceptor());

params = new BasicHttpParams();
HttpProtocolParams.setVersion(params,
                              HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params,
                                     HTTP.DEFAULT_CONTENT_CHARSET);
HttpProtocolParams.setUseExpectContinue(params,
                                        false);
return ProxyFactory.create(AndroidEquipmentResourceIF.class,
                           ProxyFactory.createUri(requestURI),
                           new ThreadSafeApacheHttpClient4Executor(params),
                           pf);
NickL
  • 4,258
  • 2
  • 21
  • 38