Search code examples
restjerseyrestlet

Restlet Client doesnt communicate well with Jersey Service


I want to realise a restlet client for my jersey service. I know that the service works, because I already developed a jersey client for it. But it seems like jersey and rest get problems to communicate with each other.

Jersey Service

Ressource:

 @Path("/object")
 @RolesAllowed({"admin", "user"})
 public class ObjectResourceBean implements ObjectResourceIF {
        @POST
        @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
        public Response postObject(JAXBElement<ObjectDTO> object) {
            ObjectDTO c = object.getValue();
            String generatedId = generateID();
            c.setObjectId(generatedId);
            c.setOwner(sec.getUserPrincipal().getName());
            return postAndGetResponse(c);
        }

        private Response postAndGetResponse(ObjectDTO object) {
            Response res;

                res = Response.created(UriBuilder.fromUri(uriInfo.getAbsolutePath() + "/" + object.getObjectId()).build()).entity(object).build();
                ObjectDAO.instance.getObjectDao().put(object.getObjectId(), object);

                System.out.println("Response: " + res);
            }
            return res;
        }

Restlet Wrap Interface:

    @Path("/object")
    @RolesAllowed({"admin", "user"})
    public interface ObjectResourceIF {
        @Post
        public Response postObject(JAXBElement<ObjectDTO> object);
 }

Already here I can't work with Jersey Annotations like @POST. I have to use @Post from Restlet Framework. But thats not the real problem.

Restlet Client

 ClientResource service = new ClientResource("http://localhost:8080/com-project-core/rest");
 service.setChallengeResponse(ChallengeScheme.HTTP_BASIC, "admin", "xxx");
 service.getReference().addSegment("object");
 // *create object*
 JAXBElement<ObjectDTO> object = new JAXBElement<ObjectDTO>(new QName("object"),    ObjectDTO.class, objectOne);
    ObjectResourceIF objectResource = service.wrap(ObjectResourceIF.class);

    List<Preference<MediaType>> acceptedMediaTypes = new ArrayList<Preference<MediaType>>();
    acceptedMediaTypes.add(new Preference(MediaType.APPLICATION_JSON));
    service.getClientInfo().setAcceptedMediaTypes(acceptedMediaTypes);

    Response res = objectResource.postObject(object);

I just get a Internal Server Error (500).

    Internal Server Error (500) - The server encountered an unexpected condition which prevented it from fulfilling the request
    at org.restlet.resource.UniformResource.toObject(UniformResource.java:649)
    at org.restlet.resource.ClientResource$1.invoke(ClientResource.java:1669)
    at $Proxy12.postObject(Unknown Source)
    at com.project.restlet.RestletConnectedTest.postObject(RestletConnectedTest.java:108)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
    Caused by: com.thoughtworks.xstream.mapper.CannotResolveClassException: objectId : objectId
    at com.thoughtworks.xstream.mapper.DefaultMapper.realClass(DefaultMapper.java:68)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.DynamicProxyMapper.realClass(DynamicProxyMapper.java:71)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.PackageAliasingMapper.realClass(PackageAliasingMapper.java:88)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.ClassAliasingMapper.realClass(ClassAliasingMapper.java:86)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.ArrayMapper.realClass(ArrayMapper.java:96)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)
    at com.thoughtworks.xstream.mapper.CachingMapper.realClass(CachingMapper.java:52)
    at com.thoughtworks.xstream.core.util.HierarchicalStreams.readClassType(HierarchicalStreams.java:29)
    at com.thoughtworks.xstream.core.TreeUnmarshaller.start(TreeUnmarshaller.java:136)
    at com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy.unmarshal(AbstractTreeMarshallingStrategy.java:33)
    at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:923)
    at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:909)
    at com.thoughtworks.xstream.XStream.fromXML(XStream.java:861)
    at org.restlet.ext.xstream.XstreamRepresentation.getObject(XstreamRepresentation.java:166)
    at org.restlet.ext.xstream.XstreamConverter.toObject(XstreamConverter.java:200)
    at org.restlet.service.ConverterService.toObject(ConverterService.java:161)
    at org.restlet.resource.UniformResource.toObject(UniformResource.java:647)
    ... 27 more

On Server Side the request gets done to

 return res;

When I try it without wrapping the Interface with

 service.post(object, MediaType.APPLICATION_JSON).write(System.out);

it works!

As I said, the Jersey Client works as well.

Maybe the transfered Class ObjectDTO is useful for the solution:

    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class ObjectDTO implements Serializable{

        /**
         * 
         */
        private static final long serialVersionUID = -8545841080597549468L;

        @XmlElement(name="objectId")
        private String objectId;
        @XmlElement(name="owner")
        private String owner;
        @XmlElement(name="objectName")
        private String objectName;

        public ObjectDTO() {

        }

        public ObjectDTO(String objectName) {
            this.objectName = objectName;
        }

        public String getObjectId() {
            return objectId;
        }

        public void setObjectId(String objectId) {
            this.objectId = objectId;
        }

        public String getOwner() {
            return owner;
        }

        public void setOwner(String owner) {
            this.owner = owner;
        }
 }

I tried it without serializable as well. Also without @XmlElement and @XmlAccessorType(XmlAccessType.FIELD). As well I tried it with

  @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})

annotation at the interface.


Solution

  • We had the same Problem with a Jersey server and Restlet client, but with a get Request. With a Restlet client to Restlet server communication we hat no problems. So the only difference we could see was, that Restlet server implementation wraps the full qualified class name of the marshalled POJO as a JSON Object around the JSON Object which contains the POJOs payload.

    package com.xyz;
    
        public class ExamplePojo implements Serializable{
    
            private static final long serialVersionUID = 1L;
    
            private String var;
    
            public ExamplePojo(){
    
            }
        //... 
        }
    

    Resulting json string on Restlet server:

    {"com.xyz.ExamplePojo":{"var":"10:23:53 30.10.2012"}}
    

    but Jersey server in its default implementation if producing MediaType.APPLICATION_JSON doesn't. Resulting json string on Jersey server:

    {"var":"10:23:53 30.10.2012"}
    

    The problem was, that if we attempt communication with the Jersey server via a Restlet client, we got the same error like in your stacktrace with the root cause:

    Caused by: com.thoughtworks.xstream.mapper.CannotResolveClassException: var : var
    

    The soulution in our case was, that we explicitly had to define the JacksonConverter if using a Restlet client with a Jersey server implementation.

    So we put the libraries from the Restlet zip on our build path of the client project

    org.restlet.ext.jackson.jar     
    org.codehaus.jackson.core.jar           
    org.codehaus.jackson.mapper.jar
    

    (interface of the resource)

    public interface PojoResource {
        @Get("json")
        public ExamplePojo retrieve();
    }
    

    and then we add explicit the JacksonConverter to the registered converters

    public ExamplePojo doRequest(){
        ClientResource cr = new ClientResource( url ); 
        // this is essential for Restlet-Jersey marshalling  
        Engine.getInstance().getRegisteredConverters().add( 0, new JacksonConverter() ); 
        PojoResource resource = cr.wrap( PojoResource.class );
        return resource.retrieve();
    }
    

    and everything worked fine.

    Note that an explicit call to

    Engine.getInstance().getRegisteredConverters().add( 0, new JacksonConverter() ); 
    

    on a Reslet-Client to Restlet-Server communication did not work, but led to an

    org.codehaus.jackson.map.JsonMappingException
    

    because of the wrapping JSON Object 'com.xyz.ExamplePojo' as an unrecognized field, which must be marked as ignoreable.

    So in your case it seems that Restlet doesn't find a converter for the unmarshalling either, so try to specify an explicit one.