|
||||||||||
| PREV CLASS NEXT CLASS | FRAMES NO FRAMES | |||||||||
| SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD | |||||||||
java.lang.Objectorg.jibx.extras.IdDefRefMapperBase
org.dellroad.stuff.jibx.IdMapper
public class IdMapper
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.
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>
JiBX and this class impose some limitations:
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).
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" ident="def"
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.
IdMappingMarshaller| Constructor Summary | |
|---|---|
IdMapper(String uri,
int index,
String name,
String className)
|
|
| Method Summary | |
|---|---|
static String |
formatId(long id)
Format the unique ID. |
protected String |
getAttributeName()
Get the ID reference attribute name. |
static String |
getId(Object obj)
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 of Object.equals() for sanity checking. |
static long |
parseId(String idref)
Parse the unique ID value assigned to the given object by getId(). |
static void |
setId(Object obj,
String idref)
Set the unique ID value for the given object. |
| Methods inherited from class org.jibx.extras.IdDefRefMapperBase |
|---|
isExtension, isPresent, unmarshal |
| Methods inherited from class java.lang.Object |
|---|
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
| Constructor Detail |
|---|
public IdMapper(String uri,
int index,
String name,
String className)
| Method Detail |
|---|
public static String getId(Object obj)
The implementation in IdMapper formats an ID of the form N012345
using the IdGenerator acquired from IdGenerator.get().
obj - any object
public static void setId(Object obj,
String idref)
The implementation in IdMapper expects an ID of the form N012345,
then associates the parsed long value with the given object
using the IdGenerator acquired from IdGenerator.get().
obj - object to registeridref - string ID assigned to the object
IllegalArgumentException - if idref is not of the form N012345
IllegalArgumentException - if idref is already associated with a different objectpublic static String formatId(long id)
id - ID value
public static long parseId(String idref)
getId().
idref - ID value assigned to the object
IllegalArgumentException - if idref is not of the form N012345protected String getIdValue(Object obj)
getId().
getIdValue in class IdDefRefMapperBaseprotected String getAttributeName()
"idref".
getAttributeName in class IdDefRefMapperBase
public void marshal(Object obj,
IMarshallingContext ictx)
throws JiBXException
Object.equals() for sanity checking.
marshal in interface IMarshallermarshal in class IdDefRefMapperBaseJiBXException
|
||||||||||
| PREV CLASS NEXT CLASS | FRAMES NO FRAMES | |||||||||
| SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD | |||||||||