Class IdMapper
- All Implemented Interfaces:
IAliasable,IMarshaller,IUnmarshaller
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 anIdGeneratoris available to generate the unique IDs (when using Spring, consider usingIdMappingMarshaller; otherwise, theJiBXUtilmethods 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 Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionstatic StringformatId(long id) Format the unique ID.protected StringGet the ID reference attribute name.static StringGet the unique ID value for the given object.protected StringgetIdValue(Object obj) Get the unique ID for the given object.voidmarshal(Object obj, IMarshallingContext ictx) Overrides superclass to use object equality instead ofObject.equals()for sanity checking.static longParse the unique ID value assigned to the given object bygetId().static voidSet the unique ID value for the given object.Methods inherited from class org.jibx.extras.IdDefRefMapperBase
isExtension, isPresent, unmarshal
-
Constructor Details
-
IdMapper
-
-
Method Details
-
getId
Get the unique ID value for the given object.The implementation in
IdMapperformats an ID of the formN012345using theIdGeneratoracquired fromIdGenerator.get().- Parameters:
obj- any object- Returns:
- unique ID for the object
-
setId
Set the unique ID value for the given object.The implementation in
IdMapperexpects an ID of the formN012345, then associates the parsedlongvalue with the given object using theIdGeneratoracquired fromIdGenerator.get().- Parameters:
obj- object to registeridref- string ID assigned to the object- Throws:
IllegalArgumentException- ifidrefis not of the formN012345IllegalArgumentException- ifidrefis already associated with a different object
-
formatId
Format the unique ID.- Parameters:
id- ID value- Returns:
- formatted idref
-
parseId
Parse the unique ID value assigned to the given object bygetId().- Parameters:
idref- ID value assigned to the object- Returns:
- parse ID number
- Throws:
IllegalArgumentException- ifidrefis not of the formN012345
-
getIdValue
Get the unique ID for the given object. Delegates togetId().- Specified by:
getIdValuein classIdDefRefMapperBase
-
getAttributeName
Get the ID reference attribute name. Default is"idref".- Overrides:
getAttributeNamein classIdDefRefMapperBase
-
marshal
Overrides superclass to use object equality instead ofObject.equals()for sanity checking.- Specified by:
marshalin interfaceIMarshaller- Overrides:
marshalin classIdDefRefMapperBase- Throws:
JiBXException
-