Difference between revisions of "EclipseLink/UserGuide/MOXy/Advanced Concepts/Virtual Access Methods"

From Eclipsepedia

Jump to: navigation, search
m (Example)
Line 92: Line 92:
 
== Example ==
 
== Example ==
  
For this example we will use the '''Employee''' class above, along with an EclipseLink OXM file to define our virtual mappings.  Any property encountered in this file that does not have a corresponding Java attribute will be considered a virtual property and will be accessed through the virtual access methods.  Because there is no associated Java field, the '''type''' information must also be provided.
+
For this example we will use the '''Customer''' class above, along with an EclipseLink OXM file to define our virtual mappings.  Any property encountered in this file that does not have a corresponding Java attribute will be considered a virtual property and will be accessed through the virtual access methods.  Because there is no associated Java field, the '''type''' information must also be provided.
  
 
'''virtualprops-oxm.xml'''
 
'''virtualprops-oxm.xml'''
Line 157: Line 157:
 
</source>
 
</source>
 
</div>
 
</div>
 
  
 
== XmlAccessType.FIELD and XmlTransient ==
 
== XmlAccessType.FIELD and XmlTransient ==

Revision as of 08:59, 22 November 2011

EclipseLink MOXy

Contents


Virtual Access Methods

In addition to standard JAXB properties (represented by Java fields and accessor methods), EclipseLink MOXy 2.3 introduces the concept of virtual properties and virtual access methods, which instead rely on special get() and set() methods to maintain mapping data. For example, you might want to use a HashMap as the underlying structure to hold data for certain mappings. The mappings that use virtual method access must be defined in EclipseLink OXM metadata.

In order to add virtual properties to an entity:

  • the Java class must be marked with an @XmlVirtualAccessMethods annotation, or <xml-virtual-access-methods> element in OXM
  • the Java class must contain getter and setter methods to access virtual property values:
    • public <ValueType> get(String propertyName)
    • public void set(String propertyName, <ValueType> value)
      • method names are configurable
      • <ValueType> can be Object, or any other Java type (if you would like to use a particular type of value class in the method signature)


Idea.png
By default, EclipseLink will look for methods named "set" and "get". To customize accessor method names, see Specifying Alternate Accessor Methods.


Configuration

Virtual Access Methods can be configured either through Java annotations or EclipseLink OXM metadata.

Using Annotations

package example;
 
import java.util.Map;
import java.util.HashMap;
 
import javax.xml.bind.annotation.*;
 
import org.eclipse.persistence.oxm.annotations.XmlVirtualAccessMethods;
 
@XmlRootElement
@XmlVirtualAccessMethods
@XmlAccessorType(XmlAccessType.PROPERTY)
public class Customer {
 
   private int id;
 
   private String name;
 
   private Map<String, Object> extensions = new HashMap<String, Object>();
 
   public Object get(String name) {
      return extensions.get(name);
   }
 
   public void set(String name, Object value) {
      extensions.put(name, value);
   }
 
   @XmlAttribute
   public int getId() {
   ...
 
}

Using EclipseLink OXM

...
<java-types>
   <java-type name="Customer">
      <xml-virtual-access-methods />
      <java-attributes>
         <xml-attribute java-attribute="id" />
         <xml-element java-attribute="name" />
      </java-attributes>
   </java-type>
...


Example

For this example we will use the Customer class above, along with an EclipseLink OXM file to define our virtual mappings. Any property encountered in this file that does not have a corresponding Java attribute will be considered a virtual property and will be accessed through the virtual access methods. Because there is no associated Java field, the type information must also be provided.

virtualprops-oxm.xml

...
<java-types>
    <java-type name="Customer">
        <java-attributes>
            <xml-element java-attribute="discountCode" name="discount-code"
                type="java.lang.String" />
        </java-attributes>
    </java-type>
</java-types>
...

When creating the JAXBContext, we pass in the virtualprops metadata along with our Customer class.

To set the values for virtual properties, we will use the aforementioned set() method.

InputStream oxm = classLoader.getResourceAsStream("virtualprops-oxm.xml");
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, oxm);
 
Class[] classes = new Class[] { Customer.class };
JAXBContext ctx = JAXBContext.newInstance(classes, properties);
 
Customer c = new Customer();
c.setId(7761);
c.setName("Bob Smith");
c.set("discountCode", "SIUB372JS7G2IUDS7");
 
ctx.createMarshaller().marshal(e, System.out);

This will produce the following XML:

<customer id="7761">
   <name>Bob Smith</name>
   <discount-code>SIUB372JS7G2IUDS7</discount-code>
</customer>

Conversely, we use the get(String) method to access virtual properties:

...
Customer c = (Customer) ctx.createUnmarshaller().unmarshal(CUSTOMER_URL);
 
// Populate UI
customerWindow.getTextField(ID).setText(String.valueOf(c.getId()));
customerWindow.getTextField(NAME).setText(c.getName());
customerWindow.getTextField(DCODE).setText(c.get("discountCode"));
...

XmlAccessType.FIELD and XmlTransient

If you are using an @XmlAccessorType of XmlAccessType.FIELD, you will need to mark your virtual properties Map attribute to be @XmlTransient, to prevent the Map itself from being bound to XML:

package example;
 
import javax.xml.bind.annotation.*;
 
import org.eclipse.persistence.oxm.annotations.XmlVirtualAccessMethods;
 
@XmlRootElement
@XmlVirtualAccessMethods
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
 
   @XmlTransient
   private Map<String, Object> extensions;
   ...


Options

Specifying Alternate Accessor Methods

To use different method names as your virtual method accessors, specify them using the getMethodName and setMethodName attributes on @XmlVirtualAccessMethods:

package example;
 
import java.util.Properties;
 
import javax.xml.bind.annotation.*;
 
import org.eclipse.persistence.oxm.annotations.XmlVirtualAccessMethods;
 
@XmlRootElement
@XmlVirtualAccessMethods(getMethod = "getCustomProps", setMethod = "putCustomProps")
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
 
   @XmlAttribute
   private int id;
 
   private String name;
 
   @XmlTransient
   private Properties<String, Object> props = new Properties<String, Object>();
 
   public Object getCustomProps(String name) {
      return props.getProperty(name);
   }
 
   public void putCustomProps(String name, Object value) {
      props.setProperty(name, value);
   }
 
}

In OXM:

...
<java-types>
  <java-type name="Customer">
    <xml-virtual-access-methods get-method="getCustomProps" set-method="putCustomProps" />
    <java-attributes>
      <xml-attribute java-attribute="id" />
      <xml-element java-attribute="name" />
      <!-- virtual -->
      <xml-element java-attribute="discountCode" name="discount-code"
        type="java.lang.String" />
    </java-attributes>
  </java-type>
...


Schema Generation Options

You can configure how virtual properties should appear in generated schemas using the schema attribute on @XmlVirtualAccessMethods. EclipseLink offers two options: virtual properties can be written as individual nodes, or consolidated into a single <any> element.


Virtual Properties as individual Nodes

This is EclipseLink's default behaviour, or can be specified explicitly as an override as follows:

package example;
 
@XmlRootElement
@XmlVirtualAccessMethods(schema = XmlVirtualAccessMethodsSchema.NODES)
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
 
   ...

For example:

Original Customer Schema:

<xs:schema ...>
 
    <xs:element name="customer">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="first-name" type="xs:string" />
                <xs:element name="last-name" type="xs:string" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>
 
</xs:schema>

Generated Schema after adding middle-initial and phone-number:

<xs:schema ...>
 
    <xs:element name="customer">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="first-name" type="xs:string" />
                <xs:element name="last-name" type="xs:string" />
                <xs:element name="middle-initial" type="xs:string" />
                <xs:element name="phone-number" type="xs:string" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>
 
</xs:schema>


Virtual Properties in an <any> Element

EclipseLink can also use an <any> element to hold all of the virtual properties in one node:

package example;
 
@XmlRootElement
@XmlVirtualAccessMethods(schema = XmlVirtualAccessMethodsSchema.ANY)
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
 
   ...

Taking the example from above, a newly generated schema using this approach would look like:

<xs:schema ...>
 
    <xs:element name="customer">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="first-name" type="xs:string" />
                <xs:element name="last-name" type="xs:string" />
                <xs:any minOccurs="0" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>
 
</xs:schema>

More Examples

For an example of using virtual properties in a multi-tenant architecture, see: