In addition to standard JAXB properties (represented by Java fields and accessor methods), EclipseLink MOXy 2.3 introduced 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)
Note: By default, EclipseLink will look for methods named |
For an example of using virtual properties in a multi-tenant architecture, see "Using Extensible MOXy".
Virtual Access Methods can be configured either by through Java annotations (see Example 8-5) or EclipseLink OXM metadata (see Example 8-6).
Example 8-5 Using Java 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() { ... }
For this example we will use the Customer class (Example 8-3), 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.
Example 8-7 The virtualprops-oxm.xml File
... <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(JAXBContextProperties.OXM_METADATA_SOURCE, 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")); ...
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:
Example 8-8 Marking the Map Attribute
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; ...
To use different method names as your virtual method accessors, specify them using the getMethodName
and setMethodName
attributes on @XmlVirtualAccessMethods
:
Example 8-9 Using Alternate Accessor Methods
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:
Example 8-10 Using the xml-virtual-access-methods Element
... <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> ...
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.
This is EclipseLink's default behavior, or can be specified explicitly as an override as follows:
Example 8-11 Mapping as Individual Nodes
package example; @XmlRootElement @XmlVirtualAccessMethods(schema = XmlVirtualAccessMethodsSchema.NODES) @XmlAccessorType(XmlAccessType.FIELD) public class Customer { ...
For example:
Example 8-12 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>
Example 8-13 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>
<any>
ElementEclipseLink can also use an <any>
element to hold all of the virtual properties in one node:
Example 8-14 Using an <any>
Element
package example; @XmlRootElement @XmlVirtualAccessMethods(schema = XmlVirtualAccessMethodsSchema.ANY) @XmlAccessorType(XmlAccessType.FIELD) public class Customer { ...
From Example 8-14, a newly generated schema using this approach would look like: