martin
martin

Reputation: 980

@XmlTransient for serialization but not for deserialisation?

Is there a way to configure the @XmlTransient JPA2.0 annotation that it will only block the JAXB mechanism when a Java-Object is serialized to xml and not when the incoming XML is transformed to a java object?

Background: I have a REST api which speaks XML. There is an endpoint to create a new Attachment object. As we speak of attachment there is a byte[] field in this class. In further listings of attachments i don't want to deliver the byte[] content of each attachment.

@Entity
@XmlRootElement
public class Attachment {

private String name;

private String mimeType;

private byte[] dataPart;

public String getName() {
    return name;
}

public void setName( String name ) {
    this.name = name;
}

public String getMimeType() {
    return mimeType;
}

public void setMimeType( String mimeType ) {
    this.mimeType = mimeType;
}

public byte[] getDataPart() {

    return dataPart.clone();
}

public void setDataPart( byte[] dataPart ) {
    this.dataPart = dataPart.clone();
}
}

So, when i mark the getDataPart() with XmlTransient, the data of the incoming byte[] is ignored and set to null -> it's lost. Anyone an idea how to specifify the direction of the XmlTransient?

Upvotes: 3

Views: 3481

Answers (4)

codigoalvo
codigoalvo

Reputation: 1

I had same problem. Your answer kind of put me in the right way. If the problem is only the serialization, this does the trick:

public class HalfDuplexXmlAdapter extends XmlAdapter<String, String> {

    @Override
    public String unmarshal(String value) throws Exception {
        return value;
    }

    @Override
    public String marshal(String value) throws Exception {
        //ignore marshall so you have half duplex
        return null;
    }

}

and then use it this way:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class User {

    String login;
    
    @XmlJavaTypeAdapter(HalfDuplexXmlAdapter.class)
    String password;

    // getters and setters
}

So this way, password send from client to server is serialized and deserialized, but when User is sent from server to client password is not serialized and/or deserialized, and so not sent (null).

Upvotes: 0

martin
martin

Reputation: 980

I have to answer the question myself =) I solved this by using a custom XmlAdapter that will tranlsate the binary data only in one direction. This is still a hack and we are not using this any more. Reasons below.

Here is the adapter:

public class DuplexBase64MarshallAdapter extends XmlAdapter<String, byte[]> {

        /**
         * running the adapter in half duplex mode means, the incoming data is marshaled but the
         * outgoing data not.
         */
        public static final boolean HALF_DUPLEX = false;

        /**
         * Running the adapter in full duplex means, the incoming and outgoing data is marshalled.
         */
        public static final boolean FULL_DUPLEX = true;

        private boolean isFullDuplexMode;

        public DuplexBase64MarshallAdapter() {
            this.isFullDuplexMode = HALF_DUPLEX;
        }

        /**
         * Constructor
         * 
         * @param fullDuplex
         *            use {@link #HALF_DUPLEX} or {@link #FULL_DUPLEX}
         */
        public DuplexBase64MarshallAdapter( boolean fullDuplex ) {
            this.isFullDuplexMode = fullDuplex;
        }

        @Override
        public byte[] unmarshal( String v ) throws Exception {
            return Base64.decode( v );
        }

        /**
         * Return always an empty string. We do not want to deliver binary content here.
         */
        @Override
        public String marshal( byte[] v ) throws Exception {
            if( isFullDuplexMode ) {
                return Base64.encodeBytes( v );
            }
            return "";
        }

    }

The entity needs to be annotated with this adapter:

@Entity
@XmlRootElement
public class Attachment {

    private String name;

    private String mimeType;

    private byte[] dataPart;

    public String getName() {
        return name;
    }

    public void setName( String name ) {
        this.name = name;
    }

    public String getMimeType() {
        return mimeType;
    }

    public void setMimeType( String mimeType ) {
        this.mimeType = mimeType;
    }

    @XmlJavaTypeAdapter( DuplexBase64MarshallAdapter.class )
    public byte[] getDataPart() {

        return dataPart.clone();
    }

    public void setDataPart( byte[] dataPart ) {
        this.dataPart = dataPart.clone();
    }
}

This solution works as exepcted. But there is a drawback: One of the intentions was to not let hibernate load the binary data while processing/loading attachment data. But thats not how it works. The binary data is loaded by hibernate in this case because it is send to the XMLAdapter but not translated into base64 :(

Upvotes: 1

Hank
Hank

Reputation: 4716

Just in case you do want to only serialize something, but never deserialize it, use @XmlAccessorType(XmlAccessType.FIELD) and only provide an @XmlElement annotated getter.

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Something {

  public Integer foo;

  public Integer getFoo() {
    return foo;
  }

  public void setFoo(Integer foo) {
    this.foo = foo;
  }

  @XmlElement
  public Integer getBar() {
    return 42;
  }
}

(Assuming foo is set to 17), this will marshall into

<something>
  <foo>17</foo>
  <bar>42</bar>
</something>

If you deserialize that (with the same class definition), bar will not be set by the unmarshaller (since it cannot find a setter).

Upvotes: 0

Hank
Hank

Reputation: 4716

I think your question is not about "yes, serialise it, but don't deserialize it", but rather: transport the data client --> server, but not server --> client. You do want your server to deserialize the incoming byte[] data, right? You just don't want to (always?) sent it out when returning the entity.

If that is the case, you can simply set it to null before you send out the entity. In your REST resource, do something like this:

@PersistenceContext(unitName = "myPersistenceUnit")
privat EntityManager em;

@Path("/upload")
@POST
public Response storeAttachment(JAXBElement<Attachment> jaxbAttachment) {
  Attachment attachment = jaxbAttachment.getValue();

  // store it in the DB
  em.persist(attachment);

  // detach it, so that modifications are no longer reflected in the database
  em.detach(attachment);

  // modify your guts out, it'll only affect this instance
  attachment.setDataPart(null);   // <-- NPE if you don't get rid of .clone() in your entity

  URI newUri = ...
  return Response.created(newUri).entity(attachment).build();
}

Upvotes: 0

Related Questions