1

(NOTE: I am sorry if the layout of this post isn't the best, I've spent quite a lot of time figuring the features of this editor)

Hi, I am doing a RESTful web project and I run into a problem returning an object that contains another object (But the object inside is literally an "Object").

In my case I have a Company, Customer and Coupon resources. Each one of then contains fields, @XMLRootElement annotation in the class level, an empty constructor (along with constructors that receives the arguments) and of course, the getters and setters.

As for the service, there are annotations in the class level:

@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)

And the get method it's self:

@GET
@Path("/myCompany)
public Message getMyCompany(){
    Message message;
    try{
        message = new MessageSuccess(company);
    } catch(Exception e){
        message = new MessageError(e.getMessage());
    }
    return message;
}

Now the way Message object is built, it's an abstract class (that contains the @XMLRootElement as well) it has three fields:

messageType (enum)
value (Object)
message (String)

it has all the features of the resource (getters and setters, construction, etc...)

And there are two classes that extending the Message. they aswell have an empty constructor and parameterized one, they don't have the @XMLRootElement annotations.

Now the problem is, when ever the client does the get method, it receives a JSON object that has

messageType: 'SUCCESS'
value: 'com.publicCodes.resources.Company@6c4sad546d'

Basically it returns a toString() of the Company object. I have no clue how to fix that. Returning servlet's Response object is not an option due to a bad practice. Returning the Company object it's self is as well not an option.

Thanks and waiting for your solutions!

**

EDIT for those who wanna see the actual code:

** Here is the Message abstract class:

package com.publicCouponRest.util;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;

@XmlRootElement
public abstract class Message {

    private MessageResultType messageType;
    private Object value;
    private String message;

    public Message() {
    }

    public Message(MessageResultType messageType, String message) {
        this.messageType = messageType;
        this.message = message;
    }

    public Message(MessageResultType messageType, Object value) {
        this.messageType = messageType;
        this.value = value;
    }

    public MessageResultType getMessageType() {
        return messageType;
    }

    public void setMessageType(MessageResultType messageType) {
        this.messageType = messageType;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

And here is MessageSuccess that extends Message:

package com.publicCouponRest.util;

public class MessageSuccess extends Message {

    public MessageSuccess() {
    }

    public MessageSuccess(Object value) {
        super(MessageResultType.SUCCESS, value);
    }

}

and of course Company resource:

package com.publicCodes.resources;

import java.util.Map;

import javax.xml.bind.annotation.XmlRootElement;

import com.publicCouponRest.services.AttributeKeys;

@XmlRootElement
public class Company {

    private long id;
    private String compName;
    private String password;
    private String email;
    private Map<Long, Coupon> coupons;
    private CompanyStatus companyStatus;
    private AttributeKeys userType = AttributeKeys.COMPANY;

    public Company(long id, String compName, String password, String email, Map<Long, Coupon> coupons, CompanyStatus companyStatus) {
        this(compName, password, email);
        this.id = id;
        this.coupons = coupons;
        this.companyStatus = companyStatus;
    }

    public Company(String compName, String password, String email) {
        super();
        this.compName = compName;
        this.password = password;
        this.email = email;
    }

    public Company() {
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getCompName() {
        return compName;
    }

    public void setCompName(String compName) {
        this.compName = compName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Map<Long, Coupon> getCoupons() {
        return coupons;
    }

    public CompanyStatus getCompanyStatus() {
        return companyStatus;
    }

    public void setCompanyStatus(CompanyStatus companyStatus) {
        this.companyStatus = companyStatus;
    }

    public void setCoupons(Map<Long, Coupon> coupons) {
        this.coupons = coupons;
    }

    public AttributeKeys getUserType() {
        return userType;
    }

    public void setUserType(AttributeKeys userType) {
        this.userType = userType;
    }

}
Young Emil
  • 1,892
  • 1
  • 19
  • 30
BobTheSatan
  • 274
  • 1
  • 14
  • Too short information: Could you be doing a .toString() when writing value: 'com.publicCodes.resources.Company@6c4sad546d'? Usually jackson returns void if it don't know how to marshall / unmarshall something... Not the result of 'toString' – inigoD Dec 13 '16 at 11:46
  • Never done toString() anywhere in my code. I've updated the post so you can see – BobTheSatan Dec 13 '16 at 12:10
  • ideally, what exactly do you want "value" to look like after it is converted to JSON? Something like {"hashCode":"fdsafafs","string":"Company@6c4sad546d"} – dave823 Dec 13 '16 at 14:12
  • I want it to give me all the attributes that the Company has like {"compName":"notWorkingComp","compEmail":"Company@gmail.com".... etc} – BobTheSatan Dec 13 '16 at 14:48
  • Being your problem JAXB I think you should put that label – inigoD Dec 13 '16 at 16:54

2 Answers2

1

Ok. I think that you are having too much fun with jackson:

You are trying to put 'whatever object' in a node. aren't you?

To do that you must use the annotation:

@XmlAnyElement(lax=false)

so something like:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement
public abstract class Message {
 ....
 @XmlAnyElement(lax=false)
 private Object value;
 ....

Should be necessary. This way you will be able to put whatever incoming XML data node in that Object (JAXB will have to know the class of that Object and that class must be annotated, but it let you manage an undetermined class)

Also (EDITED):

In the other way: Object-> XML: The problem now is that you are sending to JAXB your 'Company' object but it only sees an 'Object' because you are telling it that it's an object of type 'Object', and JAXB only know how to serialize an 'Object.class' calling to it's .toString() because Object.class hasn't got any JAXB annotation. Try returning, instead of the object, the result of this method:

(Data will be your response and clazz Company.class or whatever)

import javax.xml.bind.JAXBContext;
import javax.xml.transform.dom.DOMResult;
import org.w3c.dom.Element;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

...

public static Element marshallToElement(Object data, Class clazz) {
        DOMResult res = null;
        try {
            JAXBContext ctx = JAXBContextManager.getInstance(clazz.getPackage().getName());
            Marshaller marshaller = ctx.createMarshaller();
            res = new DOMResult();
            marshaller.marshal(data, res);
        } catch (JAXBException e) {
            LOG.error(e);
        }
        return ((Document)res.getNode()).getDocumentElement();
    }

This way you will return a JAXBElement, which is a 'bunch of nodes' that JAXB will know how to marshall.

At this point, if it works for you, it's a good practice caching the JAXBContext, it can be do saffely (JAXBContext is thread-safe, Marshallers NO) and it's a heavy duty for JAXB to execute that:

JAXBContextManager.getInstance(clazz.getPackage().getName())

So try to do it only once for each transformation.

PS:

Try putting JAXB annotations only in final classes, I'd had problems with that (because I was using annotations in an annotated subclass... And finally is cleaner to have all annotations in the same class)

inigoD
  • 1,623
  • 13
  • 24
  • but, @user2852371: you NEED to marshall/unmarshall 'any' object in that node, isn't it? If you don't try changing 'Object' with 'Company'... more than that: try doing it now in order to test if that fixes your problem (to locate the problem) – inigoD Dec 13 '16 at 14:38
  • If I changed the Object to Company and it works just fine, however I need it to be Object so it will be able to get other resources like Customer and Coupon – BobTheSatan Dec 13 '16 at 14:46
  • Well, at this point you know that you have no problem in JAX-RS and that the problem is with JAXB and the 'anytype'... May be that your ObjectFactory is not OK... Anyway: have you consider using GSON instead of jackson? (serialize with gson to JSON and sending it as String in your service ) – inigoD Dec 13 '16 at 15:09
  • The best practice is to get your projects on time ;) Anyway, programmatically is not a bad practice; the service will get an object and parse it to String to send it via HTTPConnection... In fact is something that already does and that to change it will not have repercusion in the rest of the code (is isolated so you can, if you want to, change it when you have more time in the future without break nothing)... And it will still being a REST service... – inigoD Dec 13 '16 at 15:54
  • Well thanks anyway, but just from the interest, I wonder why can't it convert a literal Object into a json.. And why it calls Object.toString(); – BobTheSatan Dec 13 '16 at 16:10
  • 1
    @user2852371 Edited, it should work and explained why. :) – inigoD Dec 13 '16 at 16:33
0

Jersey/JAX-RS 2 client

I consider you read a bit of WebTarget API, how it works and what it returns. And Also return a Response Object

And then you can change your method to this:

        @GET
        @Path("/myCompany)
        public Response getMyCompany() {

            Message message;
            try {
                message = new MessageSuccess(company);
                return Response.status(200).entity(message).build();
            } catch (Exception e) {
                message = new MessageError(e.getMessage());
                return Response.status(500).entity(message).build();
            }

        }

After that you should add this to your main method:

WebTarget target = client.target(BASE).path("myCompany");
Response response = target.request().accept(...).post(Entity.json(message));//modify this line to suit your needs
Message message = response.readEntity(Message.class);

Have tried similar thing before and I got my help from @peeskillet's answer on this stackoverflow page.

Hope it did be of Help,thank you.

Community
  • 1
  • 1
Young Emil
  • 1,892
  • 1
  • 19
  • 30