EclipseLink/Examples/MOXy/RefreshMetadata

From Eclipsepedia

Jump to: navigation, search

Contents

Introduction

This example leverages EclipseLink JAXB (MOXy)'s concepts of externalized metadata represented as a MetadataSource (see MetadataRepository example for more information), and extensible models (see Extensible example for more information). The MetadataSource will be used to define the metadata for the extensions. In EclipseLink 2.3 the ability for a JAXBContext to be "refreshed" was introduced. This means that without stopping the application the metadata can be updated to include information about new extensions.

Demo

MOXy provides a class called JAXBHelper to easily obtain MOXy's implementation of JAXBContext. This exposes the refreshMetadata() method. The refreshMetadata() call does not affect any marshal or unmarshal operations that may currently be in progress, and once the refresh is complete the new metadata will be used.

package examples.metadatasource.refresh;
 
import java.util.HashMap;
import java.util.Map;
 
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
 
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.JAXBHelper;
 
public class Demo {
 
    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>(1);
        ExtensionsMetadataSource extensions = new ExtensionsMetadataSource();
        properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, extensions);
 
        JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class}, properties);
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
 
        Customer customer = new Customer();
        customer.setFirstName("Jane");
        customer.set("middleName", "Anne");
        customer.setLastName("Doe");
 
        Address billingAddress = new Address();
        billingAddress.setStreet("123 Billing Address");
        customer.setBillingAddress(billingAddress);
 
        Address shippingAddress = new Address();
        shippingAddress.setStreet("456 Shipping Address");
        customer.set("shippingAddress", shippingAddress);
 
        marshaller.marshal(customer, System.out);
 
        extensions.addXmlElement(Customer.class, "middleName", String.class);
        extensions.addXmlElement(Customer.class, "shippingAddress", Address.class);
        JAXBHelper.getJAXBContext(jc).refeshMetadata();
 
        marshaller.marshal(customer, System.out);
    }
 
}

XML Output

In the first document that is marshalled the extensions are not included, this is because the JAXBContext is not yet aware of the metadata. Once the metadata has been added and the JAXBContext refreshed the new properties are included. One thing to note is that we did not need to obtain a new instance of Marshaller.

<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <firstName>Jane</firstName>
   <lastName>Doe</lastName>
   <billingAddress>
      <street>123 Billing Address</street>
   </billingAddress>
</customer>
<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <firstName>Jane</firstName>
   <lastName>Doe</lastName>
   <billingAddress>
      <street>123 Billing Address</street>
   </billingAddress>
  <middleName>Anne</middleName>
   <shippingAddress>
      <street>456 Shipping Address</street>
   </shippingAddress>
</customer>

ExtensionMetadataSource

In the MetadataRepository example the metadata was in the form of an XML document. In this example the metadata is provided in the form of the corresponding object model. Using the model provides us an easy means to programmatically update it. The XmlBindings model was generated from the schema for MOXy's externalized metadata format: eclipselink_oxm_2_3.xsd .

package examples.refresh;
 
import java.util.HashMap;
import java.util.Map;
 
import org.eclipse.persistence.jaxb.metadata.MetadataSourceAdapter;
import org.eclipse.persistence.jaxb.xmlmodel.JavaType;
import org.eclipse.persistence.jaxb.xmlmodel.JavaType.JavaAttributes;
import org.eclipse.persistence.jaxb.xmlmodel.ObjectFactory;
import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings;
import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings.JavaTypes;
import org.eclipse.persistence.jaxb.xmlmodel.XmlElement;
 
public class ExtensionsMetadataSource extends MetadataSourceAdapter {
 
    private ObjectFactory objectFactory;
    private Map<Class<?>, JavaType> javaTypes;
    private XmlBindings xmlBindings;
 
    public ExtensionsMetadataSource() {
        objectFactory = new ObjectFactory();
        javaTypes = new HashMap<Class<?>, JavaType>();
 
        xmlBindings = new XmlBindings();
        xmlBindings.setPackageName("examples.metadatasource.refresh");
        xmlBindings.setJavaTypes(new JavaTypes());
    }
 
    @Override
    public XmlBindings getXmlBindings(Map<String, ?> properties, ClassLoader classLoader) {
        return xmlBindings;
    }
 
    public JavaType getJavaType(Class<?> clazz) {
        JavaType javaType = javaTypes.get(clazz);
        if(null == javaType) {
            javaType = new JavaType();
            javaType.setName(clazz.getSimpleName());
            javaType.setJavaAttributes(new JavaAttributes());
            xmlBindings.getJavaTypes().getJavaType().add(javaType);
            javaTypes.put(clazz, javaType);
        }
        return javaType;
    }
 
    public void addXmlElement(Class<?> domainClass, String propertyName, Class<?> type) {
        XmlElement xmlElement = new XmlElement();
        xmlElement.setJavaAttribute(propertyName);
        xmlElement.setType(type.getName());
        JavaType javaType = getJavaType(domainClass);
        javaType.getJavaAttributes().getJavaAttribute().add(objectFactory.createXmlElement(xmlElement));
    }
 
}

Java Model with @XmlVirtualAccessMethods annotation

The @XmlVirtualAccessMethods annotation is used to specify that a class is extensible. An extensible class is required to have a "get" method that returns a value by property name, and a "set" method that stores a value by property name. The default names for these methods are "get" and "set", and can be overridden with the @XmlVirtualAccessMethods annotation.

Customer.java

package examples.metadatasource.refresh;
 
import java.util.HashMap;
import java.util.Map;
 
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
 
import org.eclipse.persistence.oxm.annotations.XmlVirtualAccessMethods;
 
@XmlRootElement
@XmlType(propOrder={"firstName", "lastName", "address"})
@XmlVirtualAccessMethods
public class Customer {
 
    private String firstName;
    private String lastName;
    private Address billingAddress;
    private Map<String, Object> extensions = new HashMap<String, Object>();
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    public Address getBillingAddress() {
        return billingAddress;
    }
 
    public void setBillingAddress(Address billingAddress) {
        this.billingAddress = billingAddress;
    }
 
    public Object get(String key) {
        return extensions.get(key);
    }
 
    public void set(String key, Object value) {
        extensions.put(key, value);
    }
 
}

Address.java

package examples.metadatasource.refresh;
 
public class Address {
 
    private String street;
 
    public String getStreet() {
        return street;
    }
 
    public void setStreet(String street) {
        this.street = street;
    }
 
}