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.
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: