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 anIdGenerator
is available to generate the unique IDs (when using Spring, consider usingIdMappingMarshaller
; otherwise, theJiBXUtil
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 Summary
-
Method Summary
Modifier and TypeMethodDescriptionstatic String
formatId
(long id) Format the unique ID.protected String
Get the ID reference attribute name.static String
Get the unique ID value for the given object.protected String
getIdValue
(Object obj) Get the unique ID for the given object.void
marshal
(Object obj, IMarshallingContext ictx) Overrides superclass to use object equality instead ofObject.equals()
for sanity checking.static long
Parse the unique ID value assigned to the given object bygetId()
.static void
Set 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
IdMapper
formats an ID of the formN012345
using theIdGenerator
acquired 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
IdMapper
expects an ID of the formN012345
, then associates the parsedlong
value with the given object using theIdGenerator
acquired fromIdGenerator.get()
.- Parameters:
obj
- object to registeridref
- string ID assigned to the object- Throws:
IllegalArgumentException
- ifidref
is not of the formN012345
IllegalArgumentException
- ifidref
is 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
- ifidref
is not of the formN012345
-
getIdValue
Get the unique ID for the given object. Delegates togetId()
.- Specified by:
getIdValue
in classIdDefRefMapperBase
-
getAttributeName
Get the ID reference attribute name. Default is"idref"
.- Overrides:
getAttributeName
in classIdDefRefMapperBase
-
marshal
Overrides superclass to use object equality instead ofObject.equals()
for sanity checking.- Specified by:
marshal
in interfaceIMarshaller
- Overrides:
marshal
in classIdDefRefMapperBase
- Throws:
JiBXException
-