4

The situation

I'm using EclipseLink's MOXy and I'm trying to use the external OX mapping XML with classes that implement the Map interface. However, every time I try create a JAXBContext, I get the following 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)

Details

This problem only occurs if the class being mapped implements the java.util.Map interface. If the class I'm mapping does not implement that interface, everything works fine. Here's a simplified example of a class I'm trying to 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");
    }
}

Here is a simplified sample of the MOXy OX meta data I'd like to use:

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

Here is how I'm creating my 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);

I'm using EclipseLink version 2.3.2, in case that's important. I've also tried with version 2.2.1 with the same results.

My Question

This the first time I've tried to use JAXB on a class that implements the java.util.Map interface and I'm curious if I'm missing something fundamental. I don't expect the OX Mappings to work with the Map's name/value pairs, but instead with the custom getters and setters added to the class.

Is a configuration like this supposed to work?

Additional Details

  1. The DelegatedMap used in the sample code does not extend java.util.HashMap, it just wraps an instance of one and implements the Map interface. Also, that class is annotated with @XmlAccessorType(XmlAccessType.NONE).
  2. I get the same error regardless of which abstract class that implements the Map interface I use for SampleClassA. If SampleClassA extends a class that does not implement a map, everything behaves correctly.
  3. The code base I'm working with requires many of the classes to implement the Map interface.
Terence
  • 706
  • 9
  • 22

1 Answers1

2

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

This is a very interesting use case. JAXB (JSR-222) has representations for maps and domain objects, so it is intersting to consider how a hybrid object should behave. I have added the following enhancement request to introduce support for it:


UPDATE

We have just finished implementing this enhancement. You can try it out using an EclipseLink 2.4.0 nightly download start on April 19, 2012 from the following location:

The fix involves leveraging the super-type property to specify a super type to override the real super type. The super-type property was previously only leveraged by our dynamic JAXB support.

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

Below is an implementation of the DelegatatedMap class as described in your question.

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

To specify MOXy as your JAXB provider you need to add a file called jaxb.properties in the same package as your domain classes with the following entry:

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

Demo

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

}

Output

Foo
Foo
<?xml version="1.0" encoding="UTF-8"?>
<SAMPLE SomeProperty="Foo"/>
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • Thank you for looking into this. I'm trying your suggestion now and I'll let you know how it goes. – Terence Apr 10 '12 at 16:16
  • @Terence - The suggestion I gave won't work, I was just showing the config for the planned fix. – bdoughan Apr 10 '12 at 17:05
  • Ahh, ok. That explains the NPE I was running into. I should have read your post more carefully! – Terence Apr 10 '12 at 17:35
  • @Terence - We have added support for this use case. I have updated my answer with the details and a complete example. – bdoughan Apr 19 '12 at 10:42