Jump to: navigation, search

Difference between revisions of "EclipseLink/Examples/MOXy/Extensible"

Line 1: Line 1:
 
== Introduction ==
 
== Introduction ==
In a multi-tenant architecture a single application runs on a server, serving multiple client organizations (tenants).  Good multi-tenant applications allow per-tenant customizations.  When these customizations are made to data, it can be difficult for the binding layer to handle them.  JAXB is designed to work with domain models that have real fields/properties.  In EclipseLink 2.3 MOXy introduces the concept of virtual properties which can easily handle this use case.  Virtual properties are defined by the MOXy metadata file, and provide a way to extend a class without modifying the source.  
+
In a multi-tenant architecture, a single application runs on a server, serving multiple client organizations (tenants).  Good multi-tenant applications allow per-tenant customizations.  When these customizations are made to data, it can be difficult for the binding layer to handle them.  JAXB is designed to work with domain models that have real fields and properties.  EclipseLink MOXy 2.3 introduces the concept of virtual properties which can easily handle this use case.  Virtual properties are defined by the MOXy metadata file, and provide a way to extend a class without modifying the source.  
  
 
== Java Model with @XmlVirtualAccessMethods annotation ==
 
== 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.  Since we will have multiple extensible classes in this example we'll configure a base class for this behaviour that extensible classes can extend.  We will use the @XmlTransient annotation to prevent ExtensibleBase from being mapped as an inheritance relationship.  The real properties represent the parts of the model that will be common to all tenants.  The per-tenant extensions will be represented as virtual properties.
+
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.  Since we will have multiple extensible classes in this example, we'll configure a base class for this behaviour that extensible classes can extend.  We will use the '''@XmlTransient''' annotation to prevent ExtensibleBase from being mapped as an inheritance relationship.  The real properties represent the parts of the model that will be common to all tenants.  The per-tenant extensions will be represented as virtual properties.
  
 
'''ExtensibleBase'''
 
'''ExtensibleBase'''
Line 34: Line 34:
 
'''Customer'''
 
'''Customer'''
  
The Customer class will be extensible since it inherits from a domain class that has been annotated with @XmlVirtualAccessMethods.
+
The Customer class will be extensible since it inherits from a domain class that has been annotated with '''@XmlVirtualAccessMethods'''.
  
 
<source lang="java">
 
<source lang="java">
Line 96: Line 96:
 
}
 
}
 
</source>
 
</source>
 +
 
'''PhoneNumber'''
 
'''PhoneNumber'''
  
PhoneNumber like Customer will be an extensible class.
+
Like Customer, PhoneNumber will be an extensible class.
  
 
<source lang="java">
 
<source lang="java">
Line 122: Line 123:
  
 
== Tenant 1 ==
 
== Tenant 1 ==
The first tenant is an online sporting goods store, that requires the following extensions to their model
+
The first tenant is an online sporting goods store, that requires the following extensions to their model:
  
 
* Customer ID
 
* Customer ID
Line 130: Line 131:
 
* Type of phone number (i.e. home, work, or cell)
 
* Type of phone number (i.e. home, work, or cell)
  
The metadata for the virtual properties is supplied through MOXy's XML mapping file.  Virtual properties are mapped in the same way as real properties.  Some additional information is required including type (since this can not be determined via reflection), and for collection properties a container type.  The virtual properties defined below for Customer are "middleName", "shippingAddress" and "phoneNumbers".  For PhoneNumber the virtual property is the "type" property.
+
The metadata for the virtual properties is supplied through MOXy's XML mapping file.  Virtual properties are mapped in the same way as real properties.  Some additional information is required including type (since this cannot be determined via reflection), and for collection properties a container type.  The virtual properties defined below for Customer are "middleName", "shippingAddress" and "phoneNumbers".  For PhoneNumber the virtual property is the "type" property.
  
 
'''binding-tenant1.xml'''
 
'''binding-tenant1.xml'''
Line 169: Line 170:
 
</source>
 
</source>
  
The get/set methods are used on the domain model to interact with the real properties and the accessors defined on the @XmlVirtualAccessMethods annotation are used to interact with the virtual properties.  The normal JAXB mechanisms are used for marshal and unmarshal operations:
+
The get/set methods are used on the domain model to interact with the real properties and the accessors defined on the '''@XmlVirtualAccessMethods''' annotation are used to interact with the virtual properties.  The normal JAXB mechanisms are used for marshal and unmarshal operations:
  
 
<source lang="java">
 
<source lang="java">
 
 
Customer customer = new Customer();
 
Customer customer = new Customer();
  
Line 228: Line 228:
 
   <phoneNumber type="HOME">555-HOME</phoneNumber>
 
   <phoneNumber type="HOME">555-HOME</phoneNumber>
 
</customer>
 
</customer>
 
 
</source>
 
</source>
  
 
==Tenant 2==
 
==Tenant 2==
The second tenant is streaming media provider that offers on-demand movies and music to it's subscribers.  It requires a different set of extensions to the core model:
+
The second tenant is a streaming media provider that offers on-demand movies and music to it's subscribers.  It requires a different set of extensions to the core model:
  
 
* A single contact phone number
 
* A single contact phone number
  
 
For this tenant we will also leverage the mapping file to customize the mapping of the real properties:
 
For this tenant we will also leverage the mapping file to customize the mapping of the real properties:
 +
 
'''binding-tenant2.xml'''
 
'''binding-tenant2.xml'''
 
 
<source lang="xml">
 
<source lang="xml">
 
<?xml version="1.0"?>
 
<?xml version="1.0"?>
Line 295: Line 294:
 
   <phoneNumber>555-WORK</phoneNumber>
 
   <phoneNumber>555-WORK</phoneNumber>
 
</customer>
 
</customer>
 
 
</source>
 
</source>

Revision as of 09:46, 15 June 2011

Introduction

In a multi-tenant architecture, a single application runs on a server, serving multiple client organizations (tenants). Good multi-tenant applications allow per-tenant customizations. When these customizations are made to data, it can be difficult for the binding layer to handle them. JAXB is designed to work with domain models that have real fields and properties. EclipseLink MOXy 2.3 introduces the concept of virtual properties which can easily handle this use case. Virtual properties are defined by the MOXy metadata file, and provide a way to extend a class without modifying the source.

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. Since we will have multiple extensible classes in this example, we'll configure a base class for this behaviour that extensible classes can extend. We will use the @XmlTransient annotation to prevent ExtensibleBase from being mapped as an inheritance relationship. The real properties represent the parts of the model that will be common to all tenants. The per-tenant extensions will be represented as virtual properties.

ExtensibleBase

package examples.virtual;
 
import java.util.HashMap;
import java.util.Map;
 
import javax.xml.bind.annotation.XmlTransient;
 
import org.eclipse.persistence.oxm.annotations.XmlVirtualAccessMethods;
 
@XmlTransient
@XmlVirtualAccessMethods(setMethod="put")
public class ExtensibleBase {
 
    private Map<String, Object> extensions = new HashMap<String, Object>();
 
    public <T> T get(String property) {
        return (T) extensions.get(property);
    }
 
    public void put(String property, Object value) {
        extensions.put(property, value);
    }
}

Customer

The Customer class will be extensible since it inherits from a domain class that has been annotated with @XmlVirtualAccessMethods.

package examples.virtual;
 
import javax.xml.bind.annotation.XmlRootElement;
 
@XmlRootElement
public class Customer extends ExtensibleBase {
 
    private String firstName;
    private String lastName;
    private Address billingAddress;
 
    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;
    }
 
}

Address

It is not necessary to have every class in your model be extensible. In this example the Address class will not have any virtual properties.

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

PhoneNumber

Like Customer, PhoneNumber will be an extensible class.

package examples.virtual;
 
import javax.xml.bind.annotation.XmlValue;
 
public class PhoneNumber extends ExtensibleBase {
 
    private String number;
 
    @XmlValue
    public String getNumber() {
        return number;
    }
 
    public void setNumber(String number) {
        this.number = number;
    }
 
}

Tenant 1

The first tenant is an online sporting goods store, that requires the following extensions to their model:

  • Customer ID
  • Customer's middle name
  • Shipping address
  • A collection of contact phone numbers
  • Type of phone number (i.e. home, work, or cell)

The metadata for the virtual properties is supplied through MOXy's XML mapping file. Virtual properties are mapped in the same way as real properties. Some additional information is required including type (since this cannot be determined via reflection), and for collection properties a container type. The virtual properties defined below for Customer are "middleName", "shippingAddress" and "phoneNumbers". For PhoneNumber the virtual property is the "type" property.

binding-tenant1.xml

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="examples.virtual">
    <java-types>
        <java-type name="Customer">
            <xml-type prop-order="firstName middleName lastName billingAddress shippingAddress phoneNumbers"/>
            <java-attributes>
                <xml-attribute
                    java-attribute="id"
                    type="java.lang.Integer"/>
                <xml-element
                    java-attribute="middleName"
                    type="java.lang.String"/>
                <xml-element
                    java-attribute="shippingAddress"
                    type="blog.multitenant.Address"/>
                <xml-element
                    java-attribute="phoneNumbers"
                    name="phoneNumber"
                    type="examples.virtual.PhoneNumber"
                    container-type="java.util.List"/>
            </java-attributes>
        </java-type>
        <java-type name="PhoneNumber">
            <java-attributes>
                <xml-attribute
                    java-attribute="type"
                    type="java.lang.String"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

The get/set methods are used on the domain model to interact with the real properties and the accessors defined on the @XmlVirtualAccessMethods annotation are used to interact with the virtual properties. The normal JAXB mechanisms are used for marshal and unmarshal operations:

Customer customer = new Customer();
 
//Set Customer's real properties
customer.setFirstName("Jane");
customer.setLastName("Doe");
 
Address billingAddress = new Address();
billingAddress.setStreet("1 Billing Street");
customer.setBillingAddress(billingAddress);
 
//Set Customer's virtual 'middleName' property
customer.put("middleName", "Anne");
 
//Set Customer's virtual 'shippingAddress' property
Address shippingAddress = new Address();
shippingAddress.setStreet("2 Shipping Road");
customer.put("shipping", shippingAddress);
 
List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
customer.put("phoneNumbers", phoneNumbers);
 
PhoneNumber workPhoneNumber = new PhoneNumber();
workPhoneNumber.setNumber("555-WORK");
//Set the PhoneNumber's virtual 'type' property
workPhoneNumber.put("type", "WORK");
phoneNumbers.add(workPhoneNumber);
 
PhoneNumber homePhoneNumber = new PhoneNumber();
homePhoneNumber.setNumber("555-HOME");
//Set the PhoneNumber's virtual 'type' property
homePhoneNumber.put("type", "HOME");
phoneNumbers.add(homePhoneNumber);
 
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "examples/virtual/binding-tenant1.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class, Address.class}, properties);
 
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(customer, System.out);

Output

<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <firstName>Jane</firstName>
   <middleName>Anne</middleName>
   <lastName>Doe</lastName>
   <billingAddress>
      <street>1 Billing Street</street>
   </billingAddress>
   <phoneNumber type="WORK">555-WORK</phoneNumber>
   <phoneNumber type="HOME">555-HOME</phoneNumber>
</customer>

Tenant 2

The second tenant is a streaming media provider that offers on-demand movies and music to it's subscribers. It requires a different set of extensions to the core model:

  • A single contact phone number

For this tenant we will also leverage the mapping file to customize the mapping of the real properties:

binding-tenant2.xml

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="examples.virtual">
    <xml-schema namespace="urn:tenant1" element-form-default="QUALIFIED"/>
    <java-types>
        <java-type name="Customer">
            <xml-type prop-order="firstName lastName billingAddress phoneNumber"/>
            <java-attributes>
                <xml-attribute java-attribute="firstName"/>
                <xml-attribute java-attribute="lastName"/>
                <xml-element java-attribute="billingAddress" name="address"/>
                <xml-element
                    java-attribute="phoneNumber"
                    type="examples.virtual.PhoneNumber"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>
Customer customer = new Customer();
customer.setFirstName("Jane");
customer.setLastName("Doe");
 
Address billingAddress = new Address();
billingAddress.setStreet("1 Billing Street");
customer.setBillingAddress(billingAddress);
 
PhoneNumber phoneNumber = new PhoneNumber();
phoneNumber.setNumber("555-WORK");
customer.put("phoneNumber", phoneNumber);
 
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "examples/virtual/binding-tenant2.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class, Address.class}, properties);
 
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(customer, System.out);

Output

Note that even though both tenants share several real properties, the corresponding XML representation can be quite different due to virtual properties:

 
<?xml version="1.0" encoding="UTF-8"?>
<customer xmlns="urn:tenant1" firstName="Jane" lastName="Doe">
   <address>
      <street>1 Billing Street</street>
   </address>
   <phoneNumber>555-WORK</phoneNumber>
</customer>