问题
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
- 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).
- 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.
- The code base I'm working with requires many of the classes to implement the Map interface.
回答1:
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:
- http://bugs.eclipse.org/37640
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:
- http://www.eclipse.org/eclipselink/downloads/nightly.php
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"/>
来源:https://stackoverflow.com/questions/10075634/npe-when-using-moxys-meta-data-with-a-class-that-implements-java-util-map