具有特定类型但未知名称的JAXB元素

时间:2017-09-27 08:45:14

标签: java xml jaxb

我有一个根Xml document (name = "Entity"),其中包含一个已知的Xml element (name = "Header")和另一个名称未知的Xml元素,但已知它有一个内部XmlElement(name="label")

以下是可能的Xmls:

<Entity>
   <Header>this is a header</Header>
   <a>
     <label>this is element A</label>
     <otherElements/>
   </a>
</Entity>

<Entity>
   <Header>this is a different header</Header>
   <b>
     <label>this is some other element of name b</label>
     <others/>
   </b>
</Entity>

以下是我的JAXB注释类:

@XmlRootElement(name = "Entity")
@XmlAccessorType(XmlAccessType.NONE)
public class Entity {
    @XmlElement(name = "Header")
    private Header header;

    @XmlElements( {
       @XmlElement(name = "a", type=LabelledElement.A.class), 
       @XmlElement(name = "b", type=LabelledElement.B.class)
    } )
    private LabelledElement labelledElement;

    // constructors, getters, setters...
}

@XmlAccessorType(XmlAccessType.NONE)
public abstract class LabelledElement {
     @XmlElement
     private String label;
     @XmlAnyElement
     private List<Element> otherElements;

     public static class A extends LabelledElement {}
     public static class B extends LabelledElement {}
}

这很棒!但后来我注意到它不仅是<a><b>

可能是<c><asd>甚至<anything> ...

因此列出XmlElement(name = "xyz", type = LabelledElement.xyz.class)显然不是正确的解决方案。

无论Entity#getLabelledElement()#getLabel()名称是什么,我所关心的只是LabelledElement

这对JAXB来说是否可行?

2 个答案:

答案 0 :(得分:1)

使用EclipseLink JAXB实现(MOXy),这应该有效:

@XmlRootElement(name = "Entity")
@XmlSeeAlso({LabelledElement.class}) //Might not be necessary
@XmlAccessorType(XmlAccessType.NONE)
public class Entity {
    @XmlElement(name = "Header")
    private Header header;

    @XmlPath("child::*[position() = 2]")
    @XmlJavaTypeAdapter(MapAdapter.class)
    private Map<String,LabelledElement> labelledElementMap;


    public LabelledElement getLabelledElement(){
         return labelledElementMap.values().get(0);
    }
    // constructors, getters, setters...
}

MapAdapter类:

public class MapAdapter extends XmlAdapter<MapAdapter.AdaptedMap, Map<String, LabelledElement>> {

    public static class AdaptedMap {

        @XmlVariableNode("key")
        List<LabbeledElement> entries = new ArrayList<LabbeledElement>();

    }

    public static class AdaptedEntry {

        @XmlTransient
        public String key;

        @XmlElement
        public LabelledElement value;

    }

    @Override
    public AdaptedMap marshal(Map<String, LabelledElement> map) throws Exception {
        AdaptedMap adaptedMap = new AdaptedMap();
        for(Entry<String, LabelledElement> entry : map.entrySet()) {
            AdaptedEntry adaptedEntry = new AdaptedEntry();
            adaptedEntry.key = entry.getKey();
            adaptedEntry.value = entry.getValue();
            adaptedMap.entries.add(adaptedEntry);
        }
        return adaptedMap;
    }

    @Override
    public Map<String, LabelledElement> unmarshal(AdaptedMap adaptedMap) throws Exception {
        List<AdaptedEntry> adaptedEntries = adaptedMap.entries;
        Map<String, LabelledElement> map = new HashMap<String, LabelledElement>();
        for(AdaptedEntry adaptedEntry : adaptedEntries) {
            map.put(adaptedEntry.key, adaptedEntry.value);
        }
        return map;
    }

}

作为参考,我的解决方案受到this link的启发。

答案 1 :(得分:0)

显然,EclipseLink JAXB(MOXy)实现可以实现,它允许您注释一个接口变量,提供在将XML绑定到Java时使用的Factory类和方法,请参阅此answer

(已编辑以提供此方法的示例) 例如,您没有抽象类LabelledElement,而是具有接口LabelledElement

public interface LabelledElement {
    String getLabel();
}

然后让类A和B实现它,如下所示:

import javax.xml.bind.annotation.XmlElement;

public class A implements LabelledElement{

    private String label;

    @Override
    @XmlElement(name="label")
    public String getLabel() {
        return label;
    }
}

实体类注释如下:

@XmlRootElement(name = "Entity")
@XmlAccessorType(XmlAccessType.NONE)
public class Entity {
    @XmlElement(name = "Header")
    private Header header;

    @XmlRootElement
    @XmlType(
    factoryClass=Factory.class, 
    factoryMethod="createLabelledElement")
    private LabelledElement labelledElement;

    // constructors, getters, setters...
}

然后,正如我所链接的答案所示,您需要工厂类,如:

import java.lang.reflect.*;
import java.util.*;

public class Factory {

    public A createA() {
        return createInstance(A.class);
    }

    public B createB() {
        return createInstance(B.class);;
    }

    private <T> T createInstance(Class<T> anInterface) {
        return (T) Proxy.newProxyInstance(anInterface.getClassLoader(), new Class[] {anInterface}, new InterfaceInvocationHandler());
    }

    private static class InterfaceInvocationHandler implements InvocationHandler {

        private Map<String, Object> values = new HashMap<String, Object>();

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            if(methodName.startsWith("get")) {
                return values.get(methodName.substring(3));
            } else {
                values.put(methodName.substring(3), args[0]);
                return null;
            }
        }

    }
}