2010-08-25 10 views
4

Je travaille avec une structure XML qui ressemble à ceci:JAXB: Cartographie des éléments de date et d'heure séparés pour une propriété

<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> 

Et je veux la carte date et l'heure à un seul Date ou Calendar propriété mon haricot. Est-ce possible en utilisant les annotations jaxb? La classe javax.xml.bind.annotation.adapters.XmlAdapter semble être en mesure de le faire, mais je dois admettre que je ne comprends pas complètement son javadoc.

Une solution serait de créer setters supplémentaires pour la date et les chaînes temps qui fixent les valeurs correspondantes dans une propriété Calendar comme ceci:

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 

Le problème (à l'exception du code supplémentaire) est que je peux Ne garantissez pas toujours que la date est définie avant la valeur de temps, mais je ne peux pas penser à un exemple spécifique où cela pourrait donner des résultats usés.

Tous les exemples pour une solution basée sur des annotations ou des améliorations/contre-exemples pour le code ci-dessus sont appréciés.

Edit: Je suis allé avec la deuxième réponse donnée par Blaise Doughan, mais a modifié son DateAttributeTransformer être plus souple et ne pas attendre le nom du champ pour contenir la chaîne « DATE ». Les noms de champs sont extraites des annotations XmlWriterTransformer sur le terrain:

@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); 
    } 
} 

Répondre

2

Au lieu d'utiliser le JAXB RI (Metro), vous pouvez utiliser la mise en œuvre Moxy JAXB (je suis le chef de file de la technologie). Il a quelques extensions qui rendront la cartographie de ce scénario assez facile.

jaxb.properties

Pour que votre Moxy utilisent la mise en œuvre de JAXB vous devez ajouter un fichier nommé JAXB.propriétés dans le même package que vos classes de modèle avec l'entrée suivante:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory 

racine

import javax.xml.bind.annotation.*; 

@XmlRootElement(name="ROOT") 
@XmlAccessorType(XmlAccessType.FIELD) 
public class Root { 

    @XmlElement(name="ELEM_A") 
    private ElemA elemA; 

    @XmlElement(name="ELEM_B") 
    private ElemB elemB; 

} 

Elema

Nous pouvons Leverate @XmlTransformation. Il est similaire dans le concept à XmlAdapter, mais plus facile à partager parmi les mappages.

import java.util.Date; 

import javax.xml.bind.annotation.XmlAccessType; 
import javax.xml.bind.annotation.XmlAccessorType; 
import javax.xml.bind.annotation.XmlElement; 

import org.eclipse.persistence.oxm.annotations.XmlReadTransformer; 
import org.eclipse.persistence.oxm.annotations.XmlTransformation; 
import org.eclipse.persistence.oxm.annotations.XmlWriteTransformer; 
import org.eclipse.persistence.oxm.annotations.XmlWriteTransformers; 

@XmlAccessorType(XmlAccessType.FIELD) 
public class ElemA { 

    @XmlTransformation 
    @XmlReadTransformer(transformerClass=DateAttributeTransformer.class) 
    @XmlWriteTransformers({ 
     @XmlWriteTransformer(xpath="A_DATE/text()", transformerClass=DateFieldTransformer.class), 
     @XmlWriteTransformer(xpath="A_TIME/text()", transformerClass=TimeFieldTransformer.class), 
    }) 
    public Date aDate; 

    @XmlElement(name="STRING") 
    private String string; 
} 

ElemB

import java.util.Date; 

import javax.xml.bind.annotation.XmlAccessType; 
import javax.xml.bind.annotation.XmlAccessorType; 
import javax.xml.bind.annotation.XmlElement; 

import org.eclipse.persistence.oxm.annotations.XmlReadTransformer; 
import org.eclipse.persistence.oxm.annotations.XmlTransformation; 
import org.eclipse.persistence.oxm.annotations.XmlWriteTransformer; 
import org.eclipse.persistence.oxm.annotations.XmlWriteTransformers; 

@XmlAccessorType(XmlAccessType.FIELD) 
public class ElemB { 

    @XmlTransformation 
    @XmlReadTransformer(transformerClass=DateAttributeTransformer.class) 
    @XmlWriteTransformers({ 
     @XmlWriteTransformer(xpath="B_DATE/text()", transformerClass=DateFieldTransformer.class), 
     @XmlWriteTransformer(xpath="B_TIME/text()", transformerClass=TimeFieldTransformer.class), 
    }) 
    private Date bDate; 

    @XmlElement(name="NUM") 
    private int num; 

    @XmlTransformation 
    @XmlReadTransformer(transformerClass=DateAttributeTransformer.class) 
    @XmlWriteTransformers({ 
     @XmlWriteTransformer(xpath="C_DATE/text()", transformerClass=DateFieldTransformer.class), 
     @XmlWriteTransformer(xpath="C_TIME/text()", transformerClass=TimeFieldTransformer.class), 
    }) 
    private Date cDate; 

} 

DateAttributeTransformer

Le transformateur d'attribut est responsable de unmarshalling l'objet Date.

import java.text.ParseException; 
import java.text.SimpleDateFormat; 

import org.eclipse.persistence.internal.helper.DatabaseField; 
import org.eclipse.persistence.mappings.foundation.AbstractTransformationMapping; 
import org.eclipse.persistence.mappings.transformers.AttributeTransformer; 
import org.eclipse.persistence.sessions.Record; 
import org.eclipse.persistence.sessions.Session; 

public class DateAttributeTransformer implements AttributeTransformer { 

    private AbstractTransformationMapping mapping; 
    private SimpleDateFormat yyyyMMddHHmmss = new SimpleDateFormat("yyyyMMddHHmmss"); 

    public void initialize(AbstractTransformationMapping mapping) { 
     this.mapping = mapping; 
    } 

    public Object buildAttributeValue(Record record, Object instance, Session session) { 
     try { 
      String dateString = null; 
      String timeString = null; 

      for(DatabaseField field : mapping.getFields()) { 
       if(field.getName().contains("DATE")) { 
        dateString = (String) record.get(field); 
       } else { 
        timeString = (String) record.get(field); 
       } 
      } 
      return yyyyMMddHHmmss.parseObject(dateString + timeString); 
     } catch(ParseException e) { 
      throw new RuntimeException(e); 
     } 
    } 

} 

DateFieldTransformer

Les transformateurs de terrain sont responsables de l'objet de triage date.

import java.text.SimpleDateFormat; 
import java.util.Date; 

import org.eclipse.persistence.mappings.foundation.AbstractTransformationMapping; 
import org.eclipse.persistence.mappings.transformers.FieldTransformer; 
import org.eclipse.persistence.sessions.Session; 

public class DateFieldTransformer implements FieldTransformer { 

    private AbstractTransformationMapping mapping; 
    private SimpleDateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd"); 

    public void initialize(AbstractTransformationMapping mapping) { 
     this.mapping = mapping; 
    } 

    public Object buildFieldValue(Object instance, String xPath, Session session) { 
     Date date = (Date) mapping.getAttributeValueFromObject(instance); 
     return yyyyMMdd.format(date); 
    } 

} 

TimeFieldTransformer

import java.text.SimpleDateFormat; 
import java.util.Date; 

import org.eclipse.persistence.mappings.foundation.AbstractTransformationMapping; 
import org.eclipse.persistence.mappings.transformers.FieldTransformer; 
import org.eclipse.persistence.sessions.Session; 

public class TimeFieldTransformer implements FieldTransformer { 

    private AbstractTransformationMapping mapping; 
    private SimpleDateFormat HHmmss = new SimpleDateFormat("HHmmss"); 

    public void initialize(AbstractTransformationMapping mapping) { 
     this.mapping = mapping; 
    } 

    public Object buildFieldValue(Object instance, String xPath, Session session) { 
     Date date = (Date) mapping.getAttributeValueFromObject(instance); 
     return HHmmss.format(date); 
    } 

} 

Exemple de programme

import java.io.File; 

import javax.xml.bind.JAXBContext; 
import javax.xml.bind.Marshaller; 
import javax.xml.bind.Unmarshaller; 

public class Demo { 

    public static void main(String[] args) throws Exception { 
     JAXBContext jc = JAXBContext.newInstance(Root.class); 
     System.out.println(jc); 

     Unmarshaller unmarshaller = jc.createUnmarshaller(); 
     File xml = new File("src/forum41/input.xml"); 
     Root root = (Root) unmarshaller.unmarshal(xml); 

     Marshaller marshaller = jc.createMarshaller(); 
     marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 
     marshaller.marshal(root, System.out); 
    } 

} 

document XML

<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> 

Le code indiqué ci-dessus nécessite EclipseLink 2.2 en cours de développement. Un nightly builds sont disponibles ici:

La version publiée actuelle de EclipseLink 2.1 prend en charge ce qui précède, mais avec une configuration légèrement différente. Nous pouvons discuter de la configuration appropriée si vous souhaitez explorer cette option.

+0

Merci encore pour la réponse complète, je vais essayer EclipseLink MOXy. –

+0

Quelques détails supplémentaires sont disponibles sur mon blog: http://bdoughan.blogspot.com/2010/08/xmltransformation-going-beyond.html –

+0

J'ai trouvé du temps pour essayer ça et ça marche très bien dans mes tests.J'ai fait quelques modifications à DateAttributeTransformer pour remplacer le contrôle 'contains' avec quelque chose d'aussi hacky mais plus flexible. Je vais ajouter la source modifiée à la question. –

5

XMLAdapter est la bonne approche:

classe à la propriété Date

import java.util.Date; 
import javax.xml.bind.annotation.XmlElement; 
import javax.xml.bind.annotation.XmlRootElement; 
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; 

@XmlRootElement(name="ROOT") 
public class Root { 

    private Date date; 

    @XmlElement(name="ELEM") 
    @XmlJavaTypeAdapter(DateAdapter.class) 
    public Date getDate() { 
     return date; 
    } 

    public void setDate(Date date) { 
     this.date = date; 
    } 

} 

La mise en œuvre de XMLAdapter

import java.text.SimpleDateFormat; 
import java.util.Date; 
import javax.xml.bind.annotation.adapters.XmlAdapter; 

public class DateAdapter extends XmlAdapter<AdaptedDate, Date> { 

    private SimpleDateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd"); 
    private SimpleDateFormat HHmmss = new SimpleDateFormat("HHmmss"); 
    private SimpleDateFormat yyyyMMddHHmmss = new SimpleDateFormat("yyyyMMddHHmmss"); 

    @Override 
    public Date unmarshal(AdaptedDate v) throws Exception { 
     String dateString = v.getDate() + v.getTime(); 
     return yyyyMMddHHmmss.parse(dateString); 
    } 

    @Override 
    public AdaptedDate marshal(Date v) throws Exception { 
     AdaptedDate adaptedDate = new AdaptedDate(); 
     adaptedDate.setDate(yyyyMMdd.format(v)); 
     adaptedDate.setTime(HHmmss.format(v)); 
     return adaptedDate; 
    } 

} 

L'objet adapté Date de

import javax.xml.bind.annotation.XmlElement; 

public class AdaptedDate { 

    private String date; 
    private String time; 

    @XmlElement(name="DATE") 
    public String getDate() { 
     return date; 
    } 

    public void setDate(String date) { 
     this.date = date; 
    } 

    @XmlElement(name="TIME") 
    public String getTime() { 
     return time; 
    } 

    public void setTime(String time) { 
     this.time = time; 
    } 

} 

Exemple de programme

import java.io.File; 

import javax.xml.bind.JAXBContext; 
import javax.xml.bind.Marshaller; 
import javax.xml.bind.Unmarshaller; 

public class Demo { 

    public static void main(String[] args) throws Exception { 
     JAXBContext jc = JAXBContext.newInstance(Root.class); 

     File xml = new File("input.xml"); 
     Unmarshaller unmarshaller = jc.createUnmarshaller(); 
     Root root = (Root) unmarshaller.unmarshal(xml); 

     Marshaller marshaller = jc.createMarshaller(); 
     marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 
     marshaller.marshal(root, System.out); 
    } 
} 

document XML

<?xml version="1.0" encoding="UTF-8"?> 
<ROOT> 
    <ELEM> 
     <DATE>20100825</DATE> 
     <TIME>141500</TIME> 
    </ELEM> 
</ROOT> 

Pour plus d'informations, voir:

+0

Merci, excellent exemple de XmlAdapter, beaucoup plus clair que l'exemple HashMap de javadoc. Mon exemple xml était un peu simpliste, je peux aussi avoir d'autres éléments et d'autres combinaisons date/heure dans cet élément. Serait-il possible d'adapter deux éléments date/heure s'ils ne sont pas enveloppés dans un autre élément? Je vais mettre à jour la question avec un exemple plus complet. –

+0

J'ai soumis une deuxième réponse qui décrit comment cela peut être fait en utilisant les extensions MOXy JAXB http://stackoverflow.com/questions/3565621/jaxb-mapping-separate-date-and-time-elements-to-one-property/ 3567927 # 3567927. –