NPE при использовании метаданных MOXy с классом, который реализует java.util.Map

Ситуация

Я использую MOXy EclipseLink и пытаюсь использовать внешний XML-отображение OX с классами, которые реализуют интерфейс Map. Однако каждый раз, когда я пытаюсь создать JAXBContext, я получаю следующий NPE:

Caused by: javax.xml.bind.JAXBException
 - with linked exception:
[java.lang.NullPointerException]
    at org.eclipse.persistence.jaxb.JAXBContext$TypeMappingInfoInput.createContextState(JAXBContext.java:832)
    at org.eclipse.persistence.jaxb.JAXBContext.<init>(JAXBContext.java:143)
    at org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(JAXBContextFactory.java:142)
    at org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(JAXBContextFactory.java:129)
    at org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(JAXBContextFactory.java:93)
    at org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(JAXBContextFactory.java:83)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:210)
    at javax.xml.bind.ContextFinder.find(ContextFinder.java:336)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)
    at com.example.MOXyOXTest<clinit>(MOXyOXTest.java:59)
Caused by: java.lang.NullPointerException
    at org.eclipse.persistence.jaxb.compiler.XMLProcessor.processXML(XMLProcessor.java:202)
    at org.eclipse.persistence.jaxb.compiler.Generator.<init>(Generator.java:145)
    at org.eclipse.persistence.jaxb.JAXBContext$TypeMappingInfoInput.createContextState(JAXBContext.java:829)

подробности

Эта проблема возникает только в том случае, если сопоставляемый класс реализует интерфейс java.util.Map. Если класс, который я отображаю, не реализует этот интерфейс, все работает нормально. Вот упрощенный пример класса, который я пытаюсь отобразить:

package com.example;

import java.util.Map;

// This class just wraps a java.util.HashMap
import com.xetus.lib.type.DelegatedMap;

public class SampleClassA extends DelegatedMap<String, Object>{

    public SampleClassA(){
        super();
    }

    public SampleClassA(Map<String, Object> m){
        super(m);
    }

    public void setSomeProperty(String value){
        put("somevalue", value);
    }

    public String getSomeProperty(){
        return (String) get("somevalue");
    }
}

Вот упрощенный образец метаданных MOXy OX, которые я хотел бы использовать:

<?xml version="1.0"?>
<xml-bindings
  xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
  package-name="com.example"
  xml-mapping-metadata-complete="true"> 
  <java-types>
  <java-type name="SampleClassA" xml-accessor-type="NONE">
   <xml-root-element name="SAMPLE" />
    <java-attributes>
     <xml-attribute type="java.lang.String" name="SomeProperty" required="true">
     <xml-access-methods get-method="getSomeProperty" set-method="setSomeProperty"/>
    </xml-attribute>
   </java-attributes>
  </java-type>
 </java-types>
</xml-bindings>

Вот как я создаю свой JAXBContext

Map<String, Object> props = new HashMap<String, Object>(1);
List bindings = new ArrayList(1);
bindings.add(new StreamSource(MOXyOXTest.class.getResourceAsStream("test-mappings.xml")));
props.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, bindings);

cntxt = JAXBContext.newInstance(new Class[] { SampleClassA.class }, props);

Я использую EclipseLink версии 2.3.2, если это важно. Я также пытался с версией 2.2.1 с теми же результатами.

Мой вопрос

Это первый раз, когда я пытался использовать JAXB в классе, который реализует интерфейс java.util.Map, и мне любопытно, упускаю ли я что-то фундаментальное. Я не ожидаю, что OX Mappings будет работать с парами имя / значение карты, но вместо этого с пользовательскими методами получения и установки, добавленными в класс.

Эта конфигурация должна работать?

дополнительные детали

  1. Делегированная карта, используемая в примере кода, не расширяет java.util.HashMap, она просто оборачивает экземпляр одного и реализует интерфейс Map. Кроме того, этот класс аннотируется @XmlAccessorType(XmlAccessType.NONE).
  2. Я получаю ту же ошибку независимо от того, какой абстрактный класс, реализующий интерфейс Map, я использую для SampleClassA. Если SampleClassA расширяет класс, который не реализует карту, все ведет себя правильно.
  3. Кодовая база, с которой я работаю, требует многих классов для реализации интерфейса Map.

1 ответ

Решение

Примечание: я являюсь лидером EclipseLink JAXB (MOXy) и являюсь членом экспертной группы JAXB (JSR-222).

Это очень интересный вариант использования. JAXB (JSR-222) имеет представления для карт и доменных объектов, поэтому интересно рассмотреть, как должен вести себя гибридный объект. Я добавил следующий запрос на улучшение для предоставления поддержки:


ОБНОВИТЬ

Мы только что закончили реализацию этого улучшения. Вы можете попробовать его с помощью ночной загрузки EclipseLink 2.4.0, которая начинается 19 апреля 2012 года, из следующего местоположения:

Исправление включает в себя использование super-type свойство, указывающее супер тип для переопределения реального супертипа. super-type свойство ранее использовалось только нашей динамической поддержкой JAXB.

bindings.xml

<?xml version="1.0"?>
<xml-bindings 
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="forum10075634">
    <java-types>
        <java-type name="SampleClassA" super-type="java.lang.Object" xml-accessor-type="NONE">
            <xml-root-element name="SAMPLE" />
            <java-attributes>
                <xml-attribute java-attribute="someProperty" name="SomeProperty" required="true"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

DelegatedMap

Ниже приведена реализация DelegatatedMap класс, как описано в вашем вопросе.

package forum10075634;

import java.util.*;

public class DelegatedMap<K,V> implements Map<K,V> {

    private Map<K,V> map;

    public DelegatedMap() {
        map = new HashMap<K,V>();
    }

    public DelegatedMap(Map<K,V> map) {
        this.map = map;
    }

    public void clear() {
        map.clear();
    }

    public boolean containsKey(Object key) {
        return map.containsKey(key);
    }

    public boolean containsValue(Object value) {
        return map.containsValue(value);
    }

    public Set<java.util.Map.Entry<K, V>> entrySet() {
        return map.entrySet();
    }

    public V get(Object key) {
        return map.get(key);
    }

    public boolean isEmpty() {
        return map.isEmpty();
    }

    public Set<K> keySet() {
        return map.keySet();
    }

    public V put(K key, V value) {
        return map.put(key, value);
    }

    public void putAll(Map<? extends K, ? extends V> m) {
        map.putAll(m);
    }

    public V remove(Object key) {
        return map.remove(key);
    }

    public int size() {
        return map.size();
    }

    public Collection<V> values() {
        return map.values();
    }

}

SampleClassA

package forum10075634;

import java.util.Map;

public class SampleClassA extends DelegatedMap<String, Object> {

    public SampleClassA() {
        super();
    }

    public SampleClassA(Map<String, Object> m) {
        super(m);
    }

    public void setSomeProperty(String value) {
        put("somevalue", value);
    }

    public String getSomeProperty() {
        return (String) get("somevalue");
    }

}

jaxb.properties

Чтобы указать MOXy в качестве вашего JAXB-провайдера, вам нужно добавить файл с именем jaxb.properties в том же пакете, что и классы вашего домена со следующей записью:

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

демонстрация

package forum10075634;

import java.io.StringReader;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>(1);
        properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum10075634/bindings.xml");
        JAXBContext jc = JAXBContext.newInstance(new Class[] {SampleClassA.class}, properties);

        StringReader xml = new StringReader("<SAMPLE SomeProperty='Foo'/>");
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        SampleClassA sampleClassA = (SampleClassA) unmarshaller.unmarshal(xml);

        System.out.println(sampleClassA.getSomeProperty());
        System.out.println(sampleClassA.get("somevalue"));

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

}

Выход

Foo
Foo
<?xml version="1.0" encoding="UTF-8"?>
<SAMPLE SomeProperty="Foo"/>
Другие вопросы по тегам