Below is the model we'll be using, notice how everything is an interface:
package example.interfaces; import java.util.List; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @XmlRootElement() @XmlType(propOrder={"name", "address"}) public interface Customer { String getName(); void setName(String name); Address getAddress(); void setAddress(Address address); public List<PhoneNumber> getPhoneNumbers(); public void setPhoneNumbers(List<PhoneNumber> phoneNumbers); }
package example.interfaces; public interface Address { public String getStreet(); public void setStreet(String street); public String getCity(); public void setCity(String city); }
package example.interfaces; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlValue; public interface PhoneNumber { @XmlAttribute() String getType(); void setType(String type); @XmlValue() String getValue(); void setValue(String value); }
We need a way to create an instance of the interface. For this we'll leverage an ObjectFactory. There is some flexibility on how this could be done, but I'll use Java's proxy mechanism with an InvocationHandler.
package example.interfaces; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; import javax.xml.bind.annotation.XmlRegistry; @XmlRegistry public class ObjectFactory { public Customer createCustomer() { return createInstance(Customer.class); } public Address createAddress() { return createInstance(Address.class); } public PhoneNumber createPhoneNumber() { return createInstance(PhoneNumber.class); } private <T> T createInstance(Class<T> anInterface) { return (T) Proxy.newProxyInstance(anInterface.getClassLoader(), new Class[] {anInterface}, new InterfaceInvocationHandler()); } private static class InterfaceInvocationHandler implements InvocationHandler { private Map<String, Object> values = new HashMap<String, Object>(); public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if(methodName.startsWith("get")) { return values.get(methodName.substring(3)); } else { values.put(methodName.substring(3), args[0]); return null; } } } }
Now we can run the following code to see everything working:
package example.interfaces; import java.io.FileInputStream; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; public class Demo { public static void main(String[] args) throws Exception { JAXBContext jaxbContext = JAXBContext.newInstance(ObjectFactory.class); FileInputStream xml = new FileInputStream("input.xml"); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); Customer customer = (Customer) unmarshaller.unmarshal(xml); ObjectFactory objectFactory = new ObjectFactory(); PhoneNumber homePhoneNumber = objectFactory.createPhoneNumber(); homePhoneNumber.setType("home"); homePhoneNumber.setValue("613-555-3333"); customer.getPhoneNumbers().add(homePhoneNumber); Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(customer, System.out); } }
To use the MOXy runtime, you need to add a file named jaxb.properties in with your model classes (or interfaces) with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Now back to Twitter to mine more topics for future blog posts.
Neat stuff Blaise.
ReplyDeleteSo this is MOXy/EclipseLink only, not standard JAXB? Even though you only use JAXB-defined types, would other JAXB implementations fail to handle this? If so, why specifically?
ReplyDeleteHi Trevor,
DeleteThe root problem with handling interfaces is that a JAXB (JSR-222) implementation requires a way to instantiate a concrete instance during unmarshalling. By default MOXy will leverage the ObjectFactory to do this if it is present, otherwise you can configure a factory class/method using the @XmlType annotation (JAXB and Factory Methods). Once MOXy knows how to instantiate a concrete instance it stops caring that it is an interface, other JAXB implementations such as the RI will thrown an exception.
-Blaise
How Could I avoid to define as many binding xml file as many class implements my interface?
ReplyDeleteE.g. If I have an interface and my class implements that interface I would like to have a way to marshal my different instances of that interface getting as output an xml or json file that in his nodes has the name of the kind of the class that has been marshalled. If I haven't been clear I'll create a question on stacked overflow.
Thank you
Sergio
Hi Sergio,
DeleteHere is a link to an answer that I gave to a similar question on Stack Overflow:
- http://stackoverflow.com/questions/16878949/moxy-jaxb-interface-annotation/16903047#16903047
-Blaise