Class IdMapper

java.lang.Object
org.jibx.extras.IdDefRefMapperBase
org.dellroad.stuff.jibx.IdMapper
All Implemented Interfaces:
IAliasable, IMarshaller, IUnmarshaller

public class IdMapper extends IdDefRefMapperBase
JiBX Marshaller/Unmarshaller that assigns unique ID's to each object and replaces duplicate appearances of the same object with an IDREF reference.

This class allows for easy ID/IDREF handling for existing classes, with minimal modifications to those classes and no custom (un)marshaller subclasses.

JiBX Mapping

Suppose you have a class Person.java with a single name property and you want to add ID/IDREF support to it.

First add the following two pseudo-bean property methods to the classes:


  import org.dellroad.stuff.jibx.IdMapper;

  public class Person {

      private String name;

      public String getName() {
          return this.name;
      }
      public void setName(String name) {
          this.name = name;
      }

      // JiBX methods
      private String getJiBXId() {
          return IdMapper.getId(this);
      }
      private void setJiBXId(String id) {
          // do nothing
      }
  }
 
Note: if you subclass Person.java from a different sub-package, you may need to change the access privileges of those methods from private to protected.

Next, define a concrete mapping for Person.java and add the id attribute:


  <mapping name="Person" class="com.example.Person">
      <value name="id" style="attribute" ident="def"
        get-method="getJiBXId" set-method="setJiBXId"/>
      <value name="name" field="name"/>
  </mapping>
 

Finally, use IdMapper as the custom marshaller and unmarshaller wherever a Person appears, e.g.:


  <mapping name="Company" class="com.example.Company">
      <collection name="Employees" field="employees" create-type="java.util.ArrayList">
          <structure name="Person" type="com.example.Person"
            marshaller="org.dellroad.stuff.jibx.IdMapper"
            unmarshaller="org.dellroad.stuff.jibx.IdMapper"/>
      </collection>
      <structure name="EmployeeOfTheWeek">
          <structure name="Person" field="employeeOfTheWeek"
            marshaller="org.dellroad.stuff.jibx.IdMapper"
            unmarshaller="org.dellroad.stuff.jibx.IdMapper"/>
      </structure>
  </mapping>
 
Note the EmployeeOfTheWeek "wrapper" element for the employeeOfTheWeek field; this is required in order to use an XML name for this field other than Person (see limitations below).

Now the first appearance of any Person will contain the full XML structure with an additional id="..." attribute, while all subsequent appearances will contain just a reference of the form <Person idref="..."/>. Conversely, when unmarshalled all Person XML elements that refer to the same original Person will re-use the same unmarshalled Person object.

So the resulting XML might look like:


  <Company>
      <Employees>
          <Person id="N00001">
              <name>Aardvark, Annie</name>
          </Person>
          <Person id="N00002">
              <name>Appleby, Arnold</name>
          </Person>
          ...
      </Employees>
      <EmployeeOfTheWeek>
          <Person idref="N00001"/>
      </EmployeeOfTheWeek>
  </Company>
 

Limitations

JiBX and this class impose some limitations:

  • JiBX marshalling must be performed within an invocation of IdGenerator.run() so that an IdGenerator is available to generate the unique IDs (when using Spring, consider using IdMappingMarshaller; otherwise, the JiBXUtil methods all satisfy this requirement).
  • Classes that use ID/IDREF must have concrete JiBX mappings.
  • All occurences of the class must use the XML element name of the concrete mapping, so the use of a "wrapper" element is required when a different element name is desired.

A Simpler Approach

The above approach is useful when you don't want to keep track of which instance of an object will appear first in the XML encoding: the first one will always fully define the object, while subsequent ones will just reference it.

If this flexibility is not needed, i.e., if you can identify where in your mapping the first occurrence of an object will appear, then the following simpler approach works without the above approach's limitations (other than requiring that marshalling be peformed within an invocation of IdGenerator.run()):

First, replace the // do nothing in the example above with call to IdMapper.setId(), and add a custom deserializer delegating to ParseUtil.deserializeReference() to


      private void setJiBXId(String id) {
          IdMapper.setId(this, id);
      }

      public static Employee deserializeEmployeeReference(String string) throws JiBXParseException {
          return ParseUtil.deserializeReference(string, Employee.class);
      }
 

Then, map the first occurrence of an object exactly as in the concrete mapping above, exposing the JiBXId property. In all subsequent occurrences of the object, expose the reference to the object as a simple property using the custom serializer/deserializer pair ParseUtil.serializeReference() and Employee.deserializeEmployeeReference().

For example, the following binding would yeild the same XML encoding as before:


  <mapping abstract="true" type-name="person" class="com.example.Person">
      <value name="id" style="attribute" get-method="getJiBXId" set-method="setJiBXId"/>
      <value name="name" field="name"/>
  </mapping>

  <mapping name="Company" class="com.example.Company">
      <collection name="Employees" field="employees" create-type="java.util.ArrayList">
          <structure name="Person" map-as="person"/>    <!-- first occurences of all these objects -->
      </collection>
      <structure name="EmployeeOfTheWeek">
          <structure name="Person">
              <value name="idref" style="attribute" field="employeeOfTheWeek"
                serializer="org.dellroad.stuff.jibx.ParseUtil.serializeReference"
                deserializer="com.example.Employee.deserializeEmployeeReference"/>
          </structure>
      </structure>
  </mapping>
 

If you want the reference to be optionally null, then you'll also need to add a test-method:


      private boolean hasEmployeeOfTheWeek() {
          return this.getEmployeeOfTheWeek() != null;
      }
 

      <structure name="EmployeeOfTheWeek" usage="optional" test-method="hasEmployeeOfTheWeek">
          <structure name="Person">
              <value name="idref" style="attribute" field="employeeOfTheWeek"
                serializer="org.dellroad.stuff.jibx.ParseUtil.serializeReference"
                deserializer="com.example.Employee.deserializeEmployeeReference"/>
          </structure>
      </structure>
 
This approach causes the whole <EmployeeOfTheWeek> element to disappear when there is no such employee. Alternately, you can avoid the need for the test-method if you want to allow just the attribute to disappear, or you could even change from style="attribute" to style="element"; in both cases you would be making the reference itself optional instead of the containing element.
See Also:
  • Constructor Details

  • Method Details