views:

36

answers:

1

What I have is a set of Java classes (close to 25) representing message types. They all inherit from a Message class which I'd like to be abstract. Each message type adds a few additional fields to the set provided by the Message superclass.

I'm implementing some RESTful web services using RESTeasy and would like to have methods like this:

public Response persist(Message msg) {
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    try {
        em.persist(msg);
    } catch (Exception e) {
        e.printStackTrace();
    }
    tx.commit();
    em.close();
    return Response.created(URI.create("/message/" + msg.getId())).build();
}

instead of having 25 separate persist methods, each tailored to a particular message type.

Currently, I've annotated my Message class like this:

@MappedSuperclass
@XmlRootElement(name = "message")
public abstract class Message implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Integer id;
    @Embedded
    Header header;
    @Embedded
    SubHeader subHeader;

My subclass then looks like this:

@Entity
@XmlRootElement(name="regmessage")
@XmlAccessorType(XmlAccessType.FIELD)
public class REGMessage extends Message {

    @XmlElement(required = true)
    int statusUpdateRate;
    @XmlElement(required = true)
    int networkRegistrationFlag;

This creates a schema which looks like it should work, but all that's seen on the server side during a persist operation is a Message object (the subtype is completely lost, or at least it isn't marshalled back into its proper subtype). On the client side, to invoke the method I do this:

REGMessage msg = new REGMessage();
// populate its fields
Response r = client.createMessage(msg);

Is what I'm attempting possible? What JAXB magic do I need to use to make the translations happen the way they should -- ie, to treat everything in Java as if it's a Message to keep the number of methods down yet still preserve all the subtype-specific information?


Thanks to Blaise's blog pointers, this now looks like it's on the road to working fully. Here's what I've got, and it does work:

//JAXB annotations
@XmlRootElement(name="message")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso(REGMessage.class)
//JPA annotations
@MappedSuperclass
public class Message {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @XmlAttribute
    private Integer id;

    private JICDHeader header;
    private int subheader;

    @XmlAnyElement
    @Transient
    private Object body;

One of the problems I encountered this morning was a cryptic error from Hibernate about the number of columns being mismatched. Once I realized that "body" was being mapped into the table, I marked it transient and voila!

@XmlRootElement(name="regmessage")
@XmlAccessorType(XmlAccessType.FIELD)
@Entity
public class REGMessage extends Message {

    private int field1;
    private int field2;

The only table generated from this code now is the regmessage table. On the RESTeasy side:

@Path("/messages")
public class MessageResource implements IMessageResource {

    private EntityManagerFactory emf;
    private EntityManager em;

    Logger logger = LoggerFactory.getLogger(MessageResource.class);

    public MessageResource() {
        try {
            emf = Persistence.createEntityManagerFactory("shepherd");
            em = emf.createEntityManager();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    @POST
    @Consumes("application/xml")
    public Response saveMessage(Message msg) {

        System.out.println(msg.toString());

        logger.info("starting saveMessage");
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            em.persist(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }

        tx.commit();
        em.close();
        logger.info("ending saveMessage");

        return Response.created(URI.create("/message/" + msg.getId())).build();
    }
}

This implements an interface:

@Path("/messages")
public interface IMessageResource {

    @GET
    @Produces("application/xml")
    @Path("{id}")
    public Message getMessage(@PathParam("id") int id);

    @POST
    @Consumes("application/xml")
    public Response saveMessage(Message msg) throws URISyntaxException;

}

Marshalling & unmarshalling work as expected, and persistence is to the subclass's table (and there is no superclass table at all).

I did see Blaise's note about JTA, which I may attempt to bring into this mix after I finish fleshing the Message & REGMessage classes back out fully.

+1  A: 

Have you tried adding the following to your message class? The @XmlSeeAlso annotation will let the JAXBContext know about the subclasses.

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

@XmlRootElement
@XmlSeeAlso(RegMessage.class)
public abstract class Message {

    Integer id;

}

Alternate Strategy:

Here is a link to a strategy I have helped people use:

Essentially you have one message object, and multiple individual message payloads. The relationship between the message and payload is handled through a @XmlAnyElement annotation.

Note on Transaction Handling

I noticed that you are handling your own transactions. Have you considered implementing your JAX-RS service as a session bean and leverage JTA for your transaction handling? For an example see:

Blaise Doughan
That looks perfect -- I'll give it a go. Thanks!
Bret
How important is it to have the java members in the superclass be defined as attributes? Mine are complex types and I'm going to try to leave them as such.
Bret
It appears to be rather important. Just converting this pretty simply and re-annotating my existing classes results in objects that, upon unmarshalling, aren't getting the body part. Those fields are all 0 or null and my type-specific fields are also getting lost.
Bret
The approach from my blog involves having a generic Message object and specialized payloads. The message object is always an instance of the same class, and the payload is a different object outside the Message hierarchy identified by the root element.
Blaise Doughan
I also noticed that in your original example, the classes don't extend the Message class, where mine do. I'm now getting an error: "org.hibernate.MappingException: property mapping has wrong number of columns: com.techma.shepherd.messages.REGMessage.body type: object" which I presume comes from the fact that my REGMessage class has inherited the body attribute from Message. Digging back into my books again.
Bret
Did you see my update about @XmlSee also, it may help your original setup.
Blaise Doughan
Bret