Sunday, October 14, 2018

JAXB Serialization Failure with Generic Class

Leave a Comment

I am trying to write a class that can serailize and deserailize settings to XML using Java. I have this code successfully written in C# and it is very useful so I would like something similar in my java app.

I have the following base class that every class I want t serialize to XML must implement.

package serializers;  import java.lang.reflect.ParameterizedType;  abstract class XmlSerializableObject<T> {      abstract T getDefault();      abstract String getSerializedFilePath();      String getGenericName() {         return ((Class<T>) ((ParameterizedType) getClass()             .getGenericSuperclass()).getActualTypeArguments()[0]).getTypeName();     }      ClassLoader getClassLoader() {         return ((Class<T>) ((ParameterizedType) getClass()             .getGenericSuperclass()).getActualTypeArguments()[0]).getClassLoader();     } } 

where the getGenericName and getClassLoader are for use with instantiating the JAXBContext. I then have a basic implementation of this as a settings provider

public class SettingsProvider extends XmlSerializableObject<SettingsProvider> {      private Settings settings;      @Override     public SettingsProvider getDefault() {         return null;     }      @Override     public String getSerializedFilePath() {         return "C:\\Data\\__tmp.settings";     }      public Settings getSettings() {         return settings;     };      public void setSettings(Settings settings) {         this.settings = settings;     } }  class Settings {      private String tmp;      public String getTmp() {         return tmp;     }      public void setTmp(String tmp) {         this.tmp = tmp;     } } 

Now I have the following serializer class

package serializers;  import org.slf4j.Logger; import org.slf4j.LoggerFactory;  import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import java.io.File;  public class XmlSerializer {      private static final Logger logger = LoggerFactory.getLogger(XmlSerializer.class);      public static <T extends XmlSerializableObject> void Serialize(T o) {          String filePath = o.getSerializedFilePath();         File file = new File(filePath);          try {             String name = o.getGenericName();             ClassLoader classLoader = o.getClassLoader();              // THE FOLLOWING LINE throws.             JAXBContext jaxbContext = JAXBContext.newInstance(name, classLoader); // also tried JAXBContext.newInstance(name);             Marshaller jaxbMarshaller = jaxbContext.createMarshaller();              jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);             jaxbMarshaller.marshal(o, file);         } catch (JAXBException e) {             logger.error("Serialization failed", e);         }     }      // Deserialize below. } 

I then have the following test to check the results of serialization

package serializers;  import org.junit.Before; import org.junit.Test;  public class XmlSerializerTest {      private Settings settings = new Settings();     private SettingsProvider provider;      @Before     public void setUp() throws Exception {         settings.setTmp("testing");         provider = new SettingsProvider();         provider.setSettings(settings);     }      @Test     public void serialize() throws Exception {         XmlSerializer.Serialize(provider);     } } 

The problem is the call to JAXBContext jaxbContext = JAXBContext.newInstance(name, classLoader); which throws

javax.xml.bind.JAXBException: Provider com.sun.xml.internal.bind.v2.ContextFactory could not be instantiated: javax.xml.bind.JAXBException: "serializers.SettingsProvider" doesnt contain ObjectFactory.class or jaxb.index - with linked exception: [javax.xml.bind.JAXBException: "serializers.SettingsProvider" doesnt contain ObjectFactory.class or jaxb.index]

I have tried with and without the ClassLoader object to no avail. How can I serialize a generic type in this way?

Thanks for your time.

4 Answers

Answers 1

Let us look at the line of code that is throwing the exception:

JAXBContext jaxbContext = JAXBContext.newInstance(name); 

In the above line of code, the argument name that you are passing is the name of the class that is to be deserialized and is determined at runtime (viz., serializers.SettingsProvider in the given sample). This may not be sufficient for JAXB to determine all the classes that constitutes the JAXB context. So instead, try passing the name of the package(s) that contain all the classes that this instance of JAXBContext should be deserializing -- all the classes in that package(s) is your JAXB context. This is something that will be known at compile time. So, try the following line of code instead:

JAXBContext jaxbContext = JAXBContext.newInstance("serializers"); 

Here, "serializers" is the name of the package that contains all the classes that you want to be deserializing, i.e., the JAXB context for the given sample.

You may like to refer the Oracle JAXB tutorial and note the following lines of code:

import primer.po.*; 

...

JAXBContext jc = JAXBContext.newInstance( "primer.po" ); 


Please refer this Javadoc and note that in case the classes to be deserialized are spread over multiple packages, then a list of colon separated package names should be passed, e.g.,--

JAXBContext.newInstance( "com.acme.foo:com.acme.bar" )  


In case you must pass class names instead of package names, then first read this Javadoc very carefully. Note that the JAXBContext instance will be initialized only with classes passed as parameter and the classes that are statically reachable from these classes. Prefer to write your program in such a way that class names being passed are known at compile time.

Also, it may be helpful for you to note that generics in Java are different (especially w.r.t type erasure) than those in C# -- please see What is the concept of erasure in generics in Java?.

Also, given the class declaration:

class XmlSerializableObject<T> { } 

which states that the class XmlSerializableObject deals with type T, the following class declaration:

class SettingsProvider extends XmlSerializableObject<SettingsProvider> { } 

which states that the class SettingsProvider deals with its own type sounds convoluted.

Or did you instead mean it to declare like as follows:

class SettingsProvider extends XmlSerializableObject<Settings> { } 

which states that the class SettingsProvider deals with type Settings?

Answers 2

That looks like it should be JAXBContext.newInstance(SettingsProvider.class) .

The JAXBContext.newInstance(String ...) versions of the method are expecting a package name, which as the error message says should then contain an ObjectFactory class, or jaxb.index list to guide it to the classes.

Answers 3

You are using this newInstance method :

Parameters:

  • contextPath - list of java package names that contain schema derived class and/or java to schema (JAXB-annotated) mapped classes

  • classLoader - This class loader will be used to locate the implementation classes.

So df778899 is right, you should not use this signature as getGenericName returns a fully qualified class name and not a package. And even if it was a package, you will still miss ObjectFactory.class or jaxb.index

But JAXBContext.newInstance(SettingsProvider.class) won't work either. You will get a MarshalException indicating that @XmlRootElement is missing

Annotate SettingsProvider like this :

@XmlRootElement(name = "root") static class SettingsProvider extends XmlSerializableObject<SettingsProvider> {      private Settings settings;      // [...] 

And finally you will get :

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <root>     <settings>         <tmp>testing</tmp>     </settings> </root> 

Answers 4

This was done by using the following interfaces

public interface IXmlSerializableObject {     String getSerializedFilePath(); } 

The crucial one being

public interface IPersistanceProvider<T> extends IXmlSerializableObject {     void save();     void restoreDefaults();     Class<T> getTypeParameterClass(); } 

The crucial property is Class<T> getTypeParameterClass(). This is then used in

public static <T extends PersistanceProviderBase> void Serialize(T o) {     String filePath = o.getSerializedFilePath();     File file = new File(filePath);     try {         JAXBContext jaxbContext = JAXBContext.newInstance(o.getTypeParameterClass());         Marshaller jaxbMarshaller = jaxbContext.createMarshaller();          jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);         jaxbMarshaller.marshal(o, file);     } catch (JAXBException e) {         logger.error("Serialization failed", e);     } } 

where PersistanceProviderBase implements the IPersistanceProvider interface.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment