Noms des balises dynamiques avec JAXB
J'utilise Jersey et JAXB pour construire un service Web simple et reposant J'ai une table de hachage de 'Chaîne' de 'Integer':
2010-04 -> 24
2010-05 -> 45
je dois générer une réponse XML qui ressemble à ceci:
<map>
<2010-04>24</2010-04>
<2010-05>45</2010-05>
</map>
Quelle est la meilleure façon de générer des noms de balises dynamiques avec JAXB?
2 réponses
vous pouvez utiliser un @XmlAnyElement
- propriété annotée et retourner les éléments comme JAXBElement
s:
private Map<String, Integer> months = ...;
@XmlAnyElement
public List<JAXBElement<Integer>> getMonths() {
List<JAXBElement<Integer>> elements = new ArrayList<JAXBElement<Integer>>();
for (Map.Entry<String, Integer> month: months.entrySet())
elements.add(new JAXBElement(new QName(month.getKey()),
Integer.class, month.getValue()));
return elements;
}
cette approche est laide, mais pas plus laide que le XML qu'elle produit.
est également entré dans ce genre de problème récemment. Après avoir référencé la réponse d'axtavt mentionnée ci-dessus (et un tas d'autres fils de questions), j'ai fait un résumé pour ce genre de problème:
- une classe de conteneur qui détient une liste (ou un tableau) de
JAXBElement
objets, où cette liste (ou tableau) est annotée avec@XmlAnyElement
, ce qui permet de générer des noms d'éléments dynamiques. - Un
XmlAdapter
de la classe qui gère formation / désapprentissage entre Carte/à partir de cette classe de conteneur. - Annoter tous les champs de carte de votre haricot java avec
@XmlJavaTypeAdapter
, avec cette classeXmlAdapter
comme valeur (ou vous pouvez simplement utiliser le classe de conteneur directement, comme vous pouvez le voir ci-dessous).
maintenant je vais prendre Map<String, String>
comme un exemple ici, où
{"key1": "value1", "key2": "value2"}
deviendra
<root>
<key1>value1</key1>
<key2>value2</key2>
</root>
ci-dessous est le code complet snippet & commentaires, ainsi que des exemples:
1, le conteneur (pour @XmlAnyElement)
/**
* <dl>
* <dt>References:
* </dt>
* <dd>
* <ul>
* <li><a href="/q/use-jaxb-xmlanyelement-type-of-style-to-return-dynamic-element-names-56763/">Dynamic element names in JAXB</a></li>
* <li><a href="/q/jaxb-how-to-marshall-map-into-value-17140/">Marshal Map into key-value pairs</a></li>
* <li><a href="/q/dynamic-tag-names-with-jaxb-35421/">Dynamic tag names with JAXB</a></li>
* </ul>
* </dd>
* </dl>
* @author MEC
*
*/
@XmlType
public static class MapWrapper{
private List<JAXBElement<String>> properties = new ArrayList<>();
public MapWrapper(){
}
/**
* <p>
* Funny fact: due to type erasure, this method may return
* List<Element> instead of List<JAXBElement<String>> in the end;
* </p>
* <h4>WARNING: do not use this method in your programme</h4>
* <p>
* Thus to retrieve map entries you've stored in this MapWrapper, it's
* recommended to use {@link #toMap()} instead.
* </p>
* @return
*/
@XmlAnyElement
public List<JAXBElement<String>> getProperties() {
return properties;
}
public void setProperties(List<JAXBElement<String>> properties) {
this.properties = properties;
}
/**
* <p>
* Only use {@link #addEntry(JAXBElement)} and {{@link #addEntry(String, String)}
* when this <code>MapWrapper</code> instance is created by yourself
* (instead of through unmarshalling).
* </p>
* @param key map key
* @param value map value
*/
public void addEntry(String key, String value){
JAXBElement<String> prop = new JAXBElement<String>(new QName(key), String.class, value);
addEntry(prop);
}
public void addEntry(JAXBElement<String> prop){
properties.add(prop);
}
@Override
public String toString() {
return "MapWrapper [properties=" + toMap() + "]";
}
/**
* <p>
* To Read-Only Map
* </p>
*
* @return
*/
public Map<String, String> toMap(){
//Note: Due to type erasure, you cannot use properties.stream() directly when unmashalling is used..
List<?> props = properties;
return props.stream().collect(Collectors.toMap(MapWrapper::extractLocalName, MapWrapper::extractTextContent));
}
/**
* <p>
* Extract local name from <code>obj</code>, whether it's javax.xml.bind.JAXBElement or org.w3c.dom.Element;
* </p>
* @param obj
* @return
*/
@SuppressWarnings("unchecked")
private static String extractLocalName(Object obj){
Map<Class<?>, Function<? super Object, String>> strFuncs = new HashMap<>();
strFuncs.put(JAXBElement.class, (jaxb) -> ((JAXBElement<String>)jaxb).getName().getLocalPart());
strFuncs.put(Element.class, ele -> ((Element) ele).getLocalName());
return extractPart(obj, strFuncs).orElse("");
}
/**
* <p>
* Extract text content from <code>obj</code>, whether it's javax.xml.bind.JAXBElement or org.w3c.dom.Element;
* </p>
* @param obj
* @return
*/
@SuppressWarnings("unchecked")
private static String extractTextContent(Object obj){
Map<Class<?>, Function<? super Object, String>> strFuncs = new HashMap<>();
strFuncs.put(JAXBElement.class, (jaxb) -> ((JAXBElement<String>)jaxb).getValue());
strFuncs.put(Element.class, ele -> ((Element) ele).getTextContent());
return extractPart(obj, strFuncs).orElse("");
}
/**
* Check class type of <code>obj</code> according to types listed in <code>strFuncs</code> keys,
* then extract some string part from it according to the extract function specified in <code>strFuncs</code>
* values.
* @param obj
* @param strFuncs
* @return
*/
private static <ObjType, T> Optional<T> extractPart(ObjType obj, Map<Class<?>, Function<? super ObjType, T>> strFuncs){
for(Class<?> clazz : strFuncs.keySet()){
if(clazz.isInstance(obj)){
return Optional.of(strFuncs.get(clazz).apply(obj));
}
}
return Optional.empty();
}
}
Notes:
- pour la reliure JAXB, tout ce que vous devez faire attention est ceci
getProperties
méthode, qui obtenir annoté par@XmlAnyElement
. - deux
addEntry
méthodes sont présentées ici pour la facilité d'utilisation. Ils devrait être utilisé avec soin cependant, comme les choses peuvent s'avérer horriblement faux lorsqu'ils sont utilisés pour unMapWrapper
parJAXBContext
(au lieu d'être créés par vous-même par l'intermédiaire d'un opérateurnew
). -
toMap
est introduit ici pour info probe, i.e. aide à vérifier entrées de carte Stockées dans cette instanceMapWrapper
.
2, L'Adaptateur (XmlAdapter)
XmlAdapter
est utilisé en paire avec @XmlJavaTypeAdapter
, qui dans ce cas est seulement nécessaire lorsque Map<String, String>
est utilisé comme une propriété de haricot.
/**
* <p>
* ref: /q/use-jaxb-xmlanyelement-type-of-style-to-return-dynamic-element-names-56763/"root")
public class CustomMap extends MapWrapper{
public CustomMap(){
}
}
Code D'Essai:
CustomMap map = new CustomMap();
map.addEntry("key1", "value1");
map.addEntry("key1", "value2");
StringWriter sb = new StringWriter();
JAXBContext.newInstance(CustomMap.class).createMarshaller().marshal(map, sb);
out.println(sb.toString());
Note que non @XmlJavaTypeAdapter
est utilisé ici.
3.2 exemple 2
pour cartographier ce xml:
<root>
<map>
<key1>value1</key1>
<key2>value2</key2>
</map>
<other>other content</other>
</root>
Vous pouvez utiliser la classe suivante:
@XmlRootElement(name="root")
@XmlType(propOrder={"map", "other"})
public class YetAnotherBean{
private Map<String, String> map = new HashMap<>();
private String other;
public YetAnotherBean(){
}
public void putEntry(String key, String value){
map.put(key, value);
}
@XmlElement(name="map")
@XmlJavaTypeAdapter(MapAdapter.class)
public Map<String, String> getMap(){
return map;
}
public void setMap(Map<String, String> map){
this.map = map;
}
@XmlElement(name="other")
public String getOther(){
return other;
}
public void setOther(String other){
this.other = other;
}
}
Code D'Essai:
YetAnotherBean yab = new YetAnotherBean();
yab.putEntry("key1", "value1");
yab.putEntry("key2", "value2");
yab.setOther("other content");
StringWriter sb = new StringWriter();
JAXBContext.newInstance(YetAnotherBean.class).createMarshaller().marshal(yab, sb);
out.println(sb.toString());
notez que @XmlJavaTypeAdapter
est appliqué sur le champ Map<String, String>
avec MapAdapter
comme valeur.
3.3 exemple 3
ajoutons maintenant quelques attributs à ces élément. Pour des raisons mystérieuses, j'ai ce genre de structure XML à cartographier:
<sys-config>
<sys-params>
<ACCESSLOG_FILE_BY attr="C" desc="AccessLog file desc">SYSTEM</ACCESSLOG_FILE_BY>
<ACCESSLOG_WRITE_MODE attr="D" desc="">DB</ACCESSLOG_WRITE_MODE>
<CHANEG_BUTTON_IMAGES attr="E" desc="Button Image URL, eh, boolean value. ...Wait, what?">FALSE</CHANEG_BUTTON_IMAGES>
</sys-params>
</sys-config>
comme vous pouvez le voir, les noms de paramètres système sont tous définis pour être le nom de l'élément au lieu de son attribut. Pour résoudre ce problème, nous pouvons utiliser un peu d'aide de JAXBElement
encore une fois:
@XmlRootElement(name="sys-config")
public class SysParamConfigXDO{
private SysParamEntries sysParams = new SysParamEntries();
public SysParamConfigXDO(){
}
public void addSysParam(String name, String value, String attr, String desc){
sysParams.addEntry(name, value, attr, desc);;
}
@XmlElement(name="sys-params")
@XmlJavaTypeAdapter(SysParamEntriesAdapter.class)
public SysParamEntries getSysParams() {
return sysParams;
}
public void setSysParams(SysParamEntries sysParams) {
this.sysParams = sysParams;
}
@Override
public String toString() {
return "SysParamConfigXDO [sysParams=" + sysParams + "]";
}
}
@XmlRootElement(name="root")
public class SysParamXDO extends SysParamEntriesWrapper{
public SysParamXDO(){
}
}
@SuppressWarnings("unchecked")
@XmlType
public class SysParamEntriesWrapper{
/**
* <p>
* Here is the tricky part:
* <ul>
* <li>When this <code>SysParamEntriesWrapper</code> is created by yourself, objects
* stored in this <code>entries</code> list is of type SystemParamEntry</li>
* <li>Yet during the unmarshalling process, this <code>SysParamEntriesWrapper</code> is
* created by the JAXBContext, thus objects stored in the <code>entries</code> is
* of type Element actually.</li>
* </ul>
* </p>
*/
List<JAXBElement<SysParamEntry>> entries = new ArrayList<>();
public SysParamEntriesWrapper(){
}
public void addEntry(String name, String value, String attr, String desc){
addEntry(new SysParamEntry(name, value, attr, desc));
}
public void addEntry(String name, String value){
addEntry(new SysParamEntry(name, value));
}
public void addEntry(SysParamEntry entry){
JAXBElement<SysParamEntry> bean = new JAXBElement<SysParamEntry>(new QName("", entry.getName()), SysParamEntry.class, entry);
entries.add(bean);
}
@XmlAnyElement
public List<JAXBElement<SysParamEntry>> getEntries() {
return entries;
}
public void setEntries(List<JAXBElement<SysParamEntry>> entries) {
this.entries = entries;
}
@Override
public String toString() {
return "SysParammEntriesWrapper [entries=" + toMap() + "]";
}
public Map<String, SysParamEntry> toMap(){
Map<String, SysParamEntry> retval = new HashMap<>();
List<?> entries = this.entries;
entries.stream().map(SysParamEntriesWrapper::convertToParamEntry).
forEach(entry -> retval.put(entry.getName(), entry));;
return retval;
}
private static SysParamEntry convertToParamEntry(Object entry){
String name = extractName(entry);
String attr = extractAttr(entry);
String desc = extractDesc(entry);
String value = extractValue(entry);
return new SysParamEntry(name, value, attr, desc);
}
@SuppressWarnings("unchecked")
private static String extractName(Object entry){
return extractPart(entry, nameExtractors).orElse("");
}
@SuppressWarnings("unchecked")
private static String extractAttr(Object entry){
return extractPart(entry, attrExtractors).orElse("");
}
@SuppressWarnings("unchecked")
private static String extractDesc(Object entry){
return extractPart(entry, descExtractors).orElse("");
}
@SuppressWarnings("unchecked")
private static String extractValue(Object entry){
return extractPart(entry, valueExtractors).orElse("");
}
private static <ObjType, RetType> Optional<RetType> extractPart(ObjType obj, Map<Class<?>,
Function<? super ObjType, RetType>> extractFuncs ){
for(Class<?> clazz : extractFuncs.keySet()){
if(clazz.isInstance(obj)){
return Optional.ofNullable(extractFuncs.get(clazz).apply(obj));
}
}
return Optional.empty();
}
private static Map<Class<?>, Function<? super Object, String>> nameExtractors = new HashMap<>();
private static Map<Class<?>, Function<? super Object, String>> attrExtractors = new HashMap<>();
private static Map<Class<?>, Function<? super Object, String>> descExtractors = new HashMap<>();
private static Map<Class<?>, Function<? super Object, String>> valueExtractors = new HashMap<>();
static{
nameExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement<SysParamEntry>)jaxb).getName().getLocalPart());
nameExtractors.put(Element.class, ele -> ((Element) ele).getLocalName());
attrExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement<SysParamEntry>)jaxb).getValue().getAttr());
attrExtractors.put(Element.class, ele -> ((Element) ele).getAttribute("attr"));
descExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement<SysParamEntry>)jaxb).getValue().getDesc());
descExtractors.put(Element.class, ele -> ((Element) ele).getAttribute("desc"));
valueExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement<SysParamEntry>)jaxb).getValue().getValue());
valueExtractors.put(Element.class, ele -> ((Element) ele).getTextContent());
}
}
public class SysParamEntriesAdapter extends XmlAdapter<SysParamEntriesWrapper, SysParamEntries>{
@Override
public SysParamEntries unmarshal(SysParamEntriesWrapper v) throws Exception {
SysParamEntries retval = new SysParamEntries();
v.toMap().values().stream().forEach(retval::addEntry);
return retval;
}
@Override
public SysParamEntriesWrapper marshal(SysParamEntries v) throws Exception {
SysParamEntriesWrapper entriesWrapper = new SysParamEntriesWrapper();
v.getEntries().forEach(entriesWrapper::addEntry);
return entriesWrapper;
}
}
public class SysParamEntries{
List<SysParamEntry> entries = new ArrayList<>();;
public SysParamEntries(){
}
public SysParamEntries(List<SysParamEntry> entries) {
super();
this.entries = entries;
}
public void addEntry(SysParamEntry entry){
entries.add(entry);
}
public void addEntry(String name, String value){
addEntry(name, value, "C");
}
public void addEntry(String name, String value, String attr){
addEntry(name, value, attr, "");
}
public void addEntry(String name, String value, String attr, String desc){
entries.add(new SysParamEntry(name, value, attr, desc));
}
public List<SysParamEntry> getEntries() {
return entries;
}
public void setEntries(List<SysParamEntry> entries) {
this.entries = entries;
}
@Override
public String toString() {
return "SystemParamEntries [entries=" + entries + "]";
}
}
@XmlType
public class SysParamEntry{
String name;
String value = "";
String attr = "";
String desc = "";
public SysParamEntry(){
}
public SysParamEntry(String name, String value) {
super();
this.name = name;
this.value = value;
}
public SysParamEntry(String name, String value, String attr) {
super();
this.name = name;
this.value = value;
this.attr = attr;
}
public SysParamEntry(String name, String value, String attr, String desc) {
super();
this.name = name;
this.value = value;
this.attr = attr;
this.desc = desc;
}
@XmlTransient
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlValue
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@XmlAttribute(name="attr")
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
this.attr = attr;
}
@XmlAttribute(name="desc")
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "SystemParamEntry [name=" + name + ", value=" + value + ", attr=" + attr + ", desc=" + desc + "]";
}
}
et c'est l'heure du test:
//Marshal
SysParamConfigXDO xdo = new SysParamConfigXDO();
xdo.addSysParam("ACCESSLOG_FILE_BY", "SYSTEM", "C", "AccessLog file desc");
xdo.addSysParam("ACCESSLOG_WRITE_MODE", "DB", "D", "");
xdo.addSysParam("CHANEG_BUTTON_IMAGES", "FALSE", "E", "Button Image URL, eh, boolean value. ...Wait, what?");
JAXBContext jaxbCtx = JAXBContext.newInstance(SysParamConfigXDO.class, SysParamEntries.class);
jaxbCtx.createMarshaller().marshal(xdo, System.out);
//Unmarshal
Path xmlFile = Paths.get("path_to_the_saved_xml_file.xml");
JAXBContext jaxbCtx = JAXBContext.newInstance(SysParamConfigXDO.class, SysParamEntries.class);
SysParamConfigXDO xdo = (SysParamConfigXDO) jaxbCtx.createUnmarshaller().unmarshal(xmlFile.toFile());
out.println(xdo.toString());