I’m working with a XML structure that looks like this:
<ROOT>
<ELEM_A>
<A_DATE>20100825</A_DATE>
<A_TIME>141500</A_TIME>
<!-- other elements, maybe also other or date/time combinations -->
<STRING>ABC</STRING>
<ELEM_A>
<ELEM_B>
<B_DATE>20100825</B_DATE>
<B_TIME>153000</B_TIME>
<NUM>123</NUM>
<C_DATE>20100825</C_DATE>
<C_TIME>154500</C_TIME>
</ELEM_B>
</ROOT>
And I want to map date and time to a single Date or Calendar property in my bean. Is this possible using jaxb annotations? The class javax.xml.bind.annotation.adapters.XmlAdapter looks like it might be able to do this, but I have to admit I don’t fully understand its javadoc.
A workaround would be to create additional setters for the date and time strings that set the corresponding values in a Calendar property like this:
private Calendar calendar;
public void setDate(String date) {
if (calendar == null) {
calendar = new GregorianCalendar();
}
calendar.set(YEAR, Integer.parseIn(date.substring(0, 4)));
calendar.set(MONTH, Integer.parseIn(date.substring(4, 6))-1);
calendar.set(DAY_OF_MONTH, Integer.parseIn(date.substring(6, 8)));
}
// Similar code for setTime
The problem (apart from the additional code) is that I can’t always guarantee that the date is set before the time value, but I can’t think of a specific example where this might give worng results.
Any examples for an annotation based solution or improvements / counter examples for the code above are appreciated.
Edit: I went with the second answer given by Blaise Doughan, but modified his DateAttributeTransformer to be more flexible and not expect the field name to contain the string “DATE”. The field names are taken from the XmlWriterTransformer annotations on the field:
@Override
public Object buildAttributeValue(Record record, Object instance, Session session) {
try {
String dateString = null;
String timeString = null;
String dateFieldName = null;
String timeFieldName = null;
// TODO: Proper Exception handling
try {
XmlWriteTransformers wts = instance.getClass().getDeclaredField(mapping.getAttributeName()).getAnnotation(XmlWriteTransformers.class);
for (XmlWriteTransformer wt : wts.value()) {
String fieldName = wt.xpath();
if (wt.transformerClass() == DateFieldTransformer.class) {
dateFieldName = fieldName;
} else {
timeFieldName = fieldName;
}
}
} catch (NoSuchFieldException ex) {
throw new RuntimeException(ex);
} catch (SecurityException ex) {
throw new RuntimeException(ex);
}
for(DatabaseField field : mapping.getFields()) {
XMLField xfield = (XMLField)field;
if(xfield.getXPath().equals(dateFieldName)) {
dateString = (String) record.get(field);
} else {
timeString = (String) record.get(field);
}
}
return yyyyMMddHHmmss.parseObject(dateString + timeString);
} catch(ParseException e) {
throw new RuntimeException(e);
}
}
Instead of using the JAXB RI (Metro), you could use the MOXy JAXB implementation (I’m the tech lead). It has some extensions that will make mapping this scenario fairly easy.
jaxb.properties
To use MOXy as your JAXB implementation you need to add a file named jaxb.properties in the same package as your model classes with the following entry:
Root
ElemA
We can leverate @XmlTransformation. It is similar in concept to XmlAdapter, but easier to share among mappings.
ElemB
DateAttributeTransformer
The attribute transformer is responsible for unmarshalling the Date object.
DateFieldTransformer
The field transformers are responsible for marshalling the Date object.
TimeFieldTransformer
Sample Program
XML Document
The code as shown above requires EclipseLink 2.2 currently under development. A nightly builds are available here:
The current released version of EclipseLink 2.1 supports the above, but with a slightly different configuration. We can discuss the appropriate setup if you are interested in exploring this option.