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.
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
?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With