I want to achieve the following xml using simple xml framework (http://simple.sourceforge.net/):
<events>
<course-added date="01/01/2010">
...
</course-added>
<course-removed date="01/02/2010">
....
</course-removed>
<student-enrolled date="01/02/2010">
...
</student-enrolled>
</events>
I have the following (but it doesn't achieve the desired xml):
#Root(name="events")
class XMLEvents {
#ElementList(inline=true)
ArrayList<XMLEvent> events = Lists.newArrayList();
...
}
abstract class XMLEvent {
#Attribute(name="date")
String dateOfEventFormatted;
...
}
And different type of XMLNodes that have different information (but are all different types of events)
#Root(name="course-added")
class XMLCourseAdded extends XMLEvent{
#Element(name="course")
XMLCourseLongFormat course;
....
}
#Root(name="course-removed")
class XMLCourseRemoved extends XMLEvent {
#Element(name="course-id")
String courseId;
...
}
How should I do the mapping or what should I change in order to be able to achieve de desired xml?
Thanks!
A very clean way to solve the problem is to create your own converter such as:
public class XMLEventsConverter implements Converter<XMLEvents> {
private Serializer serializer;
XMLEventsConverter(Serializer serializer){
this.serializer = serializer;
}
#Override
public XMLEvents read(InputNode arg0) throws Exception {
return null;
}
#Override
public void write(OutputNode node, XMLEvents xmlEvents) throws Exception {
for (XMLEvent event : xmlEvents.events) {
serializer.write(event, node);
}
}
}
And then use a RegistryStrategy and bind the class XMLEvents with the previous converter:
private final Registry registry = new Registry();
private final Serializer serializer = new Persister(new RegistryStrategy(registry));
....
registry.bind(XMLEvents.class, new XMLEventsConverter(serializer));
In this way the xml obtained is the one desired. Note that the read method on the XMLEventsConverter just return null, in case you need to rebuild the object from the xml file you should properly implement it.
Regards!
Looks like you can do this now using the #ElementListUnion functionality
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.ElementListUnion;
import org.simpleframework.xml.core.Persister;
import java.io.StringReader;
import java.util.List;
/**
* Created by dan on 3/09/16.
*/
public class XMLEventsTest {
public static final String XML_EVENTS = "<?xml version=\"1.0\" " +
"encoding=\"ISO-8859-1\"?><events>" +
"<course-added date=\"2016/10/1\"/>" +
"<course-removed date=\"2016/10/2\"/>" +
"</events>";
public static void main(String args[]) throws Exception {
Persister persister = new Persister();
XMLEvents events = persister.read(XMLEvents.class, new StringReader(XML_EVENTS));
for (XMLEvent e : events.events) {
System.out.println("found event: " + e);
}
}
public static class XMLEvents {
#ElementListUnion({
#ElementList(entry = "course-added", type = XMLCourseAdded.class, inline = true),
#ElementList(entry = "course-removed", type = XMLCourseRemoved.class, inline = true),
})
private List<XMLEvent> events;
}
public static class XMLEvent {
#Attribute(name = "date")
private String dateOfEventFormatted;
public String getDateOfEventFormatted() {
return dateOfEventFormatted;
}
#Override
public String toString() {
return getClass().getSimpleName() + "[" + dateOfEventFormatted + "]";
}
}
public static class XMLCourseAdded extends XMLEvent {
}
public static class XMLCourseRemoved extends XMLEvent {
}
}
This will print out:
found event: XMLCourseAdded[2016/10/1]
found event: XMLCourseRemoved[2016/10/2]
Related
When serialising objects to XML and specifying namespaces for properties using
#JacksonXmlRootElement(namespace = "http://...")
Jackson will append or prepend ´wstxns1´ to the namespace. For example, say we have these classes:
VtexSkuAttributeValues.java
#JacksonXmlRootElement(localName = "listStockKeepingUnitName")
public class VtexSkuAttributeValues {
#JacksonXmlProperty(localName = "StockKeepingUnitFieldNameDTO", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
#JacksonXmlElementWrapper(useWrapping = false)
private VtexSkuAttributeValue[] stockKeepingUnitFieldNameDTO;
public VtexSkuAttributeValue[] getStockKeepingUnitFieldNameDTO() {
return stockKeepingUnitFieldNameDTO;
}
public void setValues(VtexSkuAttributeValue[] values) {
this.stockKeepingUnitFieldNameDTO = values;
}
}
VtexSkuAttributeValue.java
#JacksonXmlRootElement(localName = "StockKeepingUnitFieldNameDTO", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
public class VtexSkuAttributeValue {
private String fieldName;
private FieldValues fieldValues;
private int idSku;
public int getIdSku() {
return idSku;
}
public String getFieldName() {
return fieldName;
}
public FieldValues getFieldValues() {
return fieldValues;
}
public void setIdSku(int idSku) {
this.idSku = idSku;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public void setFieldValues(FieldValues fieldValues) {
this.fieldValues = fieldValues;
}
#JacksonXmlRootElement(localName = "fieldValues", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
public static class FieldValues {
#JacksonXmlProperty(namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")
#JacksonXmlElementWrapper(useWrapping = false)
public String[] string;
public String[] getString() {
return string;
}
public void setValues(String[] values) {
this.string = values;
}
}
}
I then use the XmlMapper to serialise and get:
<listStockKeepingUnitName>
<wstxns1:StockKeepingUnitFieldNameDTO xmlns:wstxns1="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>talle</fieldName>
<fieldValues>
<wstxns2:string xmlns:wstxns2="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</wstxns2:string>
</fieldValues>
<idSku>258645</idSku>
</wstxns1:StockKeepingUnitFieldNameDTO>
<wstxns3:StockKeepingUnitFieldNameDTO xmlns:wstxns3="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>color</fieldName>
<fieldValues>
<wstxns4:string xmlns:wstxns4="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6244</wstxns4:string>
</fieldValues>
<idSku>258645</idSku>
</wstxns3:StockKeepingUnitFieldNameDTO>
</listStockKeepingUnitName>
Even though this is valid XML, the web service I'm working with doesn't accept it. I debugged it and it's due to the wstxns properties in the tags that Jackson adds for some reason.
Is there a way to prevent Jackson from adding that to the tags. The only workaround I could come up with is performing a string.replaceAll on the resulting XML but it's obviously not ideal.
To write XML Jackson uses javax.xml.stream.XMLStreamWriter. You can configure instance of that class and define your own prefixes for namespaces and set default one if needed. To do that we need to extend com.fasterxml.jackson.dataformat.xml.XmlFactory class and override a method which creates XMLStreamWriter instance. Example implementation could look like below:
class NamespaceXmlFactory extends XmlFactory {
private final String defaultNamespace;
private final Map<String, String> prefix2Namespace;
public NamespaceXmlFactory(String defaultNamespace, Map<String, String> prefix2Namespace) {
this.defaultNamespace = Objects.requireNonNull(defaultNamespace);
this.prefix2Namespace = Objects.requireNonNull(prefix2Namespace);
}
#Override
protected XMLStreamWriter _createXmlWriter(IOContext ctxt, Writer w) throws IOException {
XMLStreamWriter writer = super._createXmlWriter(ctxt, w);
try {
writer.setDefaultNamespace(defaultNamespace);
for (Map.Entry<String, String> e : prefix2Namespace.entrySet()) {
writer.setPrefix(e.getKey(), e.getValue());
}
} catch (XMLStreamException e) {
StaxUtil.throwAsGenerationException(e, null);
}
return writer;
}
}
You can use it as below:
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlFactory;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
public class XmlMapperApp {
public static void main(String[] args) throws Exception {
String defaultNamespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts";
Map<String, String> otherNamespaces = Collections.singletonMap("a", "http://schemas.microsoft.com/2003/10/Serialization/Arrays");
XmlMapper xmlMapper = new XmlMapper(new NamespaceXmlFactory(defaultNamespace, otherNamespaces));
xmlMapper.enable(SerializationFeature.INDENT_OUTPUT);
System.out.println(xmlMapper.writeValueAsString(new VtexSkuAttributeValues()));
}
}
In VtexSkuAttributeValues class you can declare:
public static final String DEF_NMS = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts";
and use it for every class and field where it should be used as default namespace. For example:
#JacksonXmlProperty(localName = "StockKeepingUnitFieldNameDTO", namespace = DEF_NMS)
For properties, for which you do not want to change name you can use:
#JacksonXmlProperty(namespace = VtexSkuAttributeValues.DEF_NMS)
Above code prints for some random data:
<listStockKeepingUnitName>
<StockKeepingUnitFieldNameDTO xmlns="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>Name1</fieldName>
<fieldValues>
<a:string xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</a:string>
</fieldValues>
<idSku>123</idSku>
</StockKeepingUnitFieldNameDTO>
<StockKeepingUnitFieldNameDTO xmlns="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>Name1</fieldName>
<fieldValues>
<a:string xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</a:string>
</fieldValues>
<idSku>123</idSku>
</StockKeepingUnitFieldNameDTO>
</listStockKeepingUnitName>
If it is not what you want you can play with that code and try other methods which are available for you to configure this instance.
To create this example Jackson in version 2.9.9 was used.
This seems to be the missing piece. It allows you to set the prefix and namespace.
static class NamespaceXmlFactory extends XmlFactory {
private final String defaultNamespace;
private final Map<String, String> prefix2Namespace;
public NamespaceXmlFactory(String defaultNamespace, Map<String, String> prefix2Namespace) {
this.defaultNamespace = Objects.requireNonNull(defaultNamespace);
this.prefix2Namespace = Objects.requireNonNull(prefix2Namespace);
}
#Override
protected XMLStreamWriter _createXmlWriter(IOContext ctxt, Writer w) throws IOException {
XMLStreamWriter2 writer = (XMLStreamWriter2)super._createXmlWriter(ctxt, w);
try {
writer.setDefaultNamespace(defaultNamespace);
writer.setPrefix("xsi", "http://www.w3.org/2001/XMLSchema-instance");
for (Map.Entry<String, String> e : prefix2Namespace.entrySet()) {
writer.setPrefix(e.getKey(), e.getValue());
}
} catch (XMLStreamException e) {
StaxUtil.throwAsGenerationException(e, null);
}
return writer;
}
}
The only remaining issue I have is
#JacksonXmlProperty(localName = "#xsi.type", isAttribute = true, namespace = "http://www.w3.org/2001/XMLSchema-instance")
#JsonProperty("#xsi.type")
private String type;
Creates the following output:
Still trying to resolve how to make it be xsi:type="networkObjectGroupDTO" instead.
I am using https://mongodb.github.io/mongo-java-driver-reactivestreams/1.11/. It seems to be using https://mongodb.github.io/mongo-java-driver/3.10/. I have a bunch of other registered classes that are working fine. I am using the suggestions at https://mongodb.github.io/mongo-java-driver/3.5/bson/pojos/ (and Save List of interface objects using mongo driver for java) for dealing with fields that have interfaces. However, I get the below error. For other classes for which I get this error, I can simply add an empty constructor to the class, but I cannot do so for an interface. Any help would be appreciated.
Caused by: org.bson.codecs.configuration.CodecConfigurationException: Failed to decode 'SearchCriteria'. Decoding 'filters' errored with: Cannot find a public constructor for 'FilterInterface'.
at org.bson.codecs.pojo.PojoCodecImpl.decodePropertyModel(PojoCodecImpl.java:222)
at org.bson.codecs.pojo.PojoCodecImpl.decodeProperties(PojoCodecImpl.java:197)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:121)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:125)
at org.bson.codecs.pojo.LazyPojoCodec.decode(LazyPojoCodec.java:57)
at org.bson.codecs.DecoderContext.decodeWithChildContext(DecoderContext.java:93)
at org.bson.codecs.pojo.PojoCodecImpl.decodePropertyModel(PojoCodecImpl.java:213)
... 36 common frames omitted
Caused by: org.bson.codecs.configuration.CodecConfigurationException: Cannot find a public constructor for 'FilterInterface'.
at org.bson.codecs.pojo.CreatorExecutable.checkHasAnExecutable(CreatorExecutable.java:140)
at org.bson.codecs.pojo.CreatorExecutable.getInstance(CreatorExecutable.java:107)
at org.bson.codecs.pojo.InstanceCreatorImpl.<init>(InstanceCreatorImpl.java:40)
at org.bson.codecs.pojo.InstanceCreatorFactoryImpl.create(InstanceCreatorFactoryImpl.java:28)
at org.bson.codecs.pojo.ClassModel.getInstanceCreator(ClassModel.java:71)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:120)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:125)
at org.bson.codecs.pojo.CollectionPropertyCodecProvider$CollectionCodec.decode(CollectionPropertyCodecProvider.java:74)
at org.bson.codecs.pojo.CollectionPropertyCodecProvider$CollectionCodec.decode(CollectionPropertyCodecProvider.java:43)
at org.bson.codecs.DecoderContext.decodeWithChildContext(DecoderContext.java:93)
at org.bson.codecs.pojo.PojoCodecImpl.decodePropertyModel(PojoCodecImpl.java:213)
... 42 common frames omitted
Below are snippets of my code:
#BsonDiscriminator
public interface FilterInterface<T> {
boolean applyOn(T value);
T getValue();
...
}
public abstract class Filter<T> implements FilterInterface<T> {
public Filter() { }
public abstract boolean applyOn(T value);
public abstract T getValue();
...
}
public class AddressFilter extends Filter<Address> {
public AddressFilter() { }
public boolean applyOn(Address value) {
return true;
}
public Address getValue() {
return new Address();
}
...
}
public class SearchCriteria {
public SearchCriteria() { }
private List<FilterInterface> filters;
}
public static void init() {
String url = <hidden>;
MongoClient mongoClient = MongoClients.create(new ConnectionString(url));
// For POJOs here
// For interface classes.
PojoCodecProvider pojoCodecProvider = PojoCodecProvider.builder()
.conventions(ImmutableList.of(CLASS_AND_PROPERTY_CONVENTION, ANNOTATION_CONVENTION))
.register(SearchCriteria.class)
.register(
ClassModel.builder(FilterInterface.class).enableDiscriminator(true).build(),
ClassModel.builder(Filter.class).enableDiscriminator(true).build(),
ClassModel.builder(AddressFilter.class).enableDiscriminator(true).build())
.automatic(true)
.build();
CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
MongoClientSettings.getDefaultCodecRegistry(),
CodecRegistries.fromProviders(pojoCodecProvider));
String dbName = <hidden>;
mongoDb = mongoClient.getDatabase(dbName).withCodecRegistry(codecRegistry);
}
The example provided in the link works perfectly fine. Much credit goes to that user for this answer.
You have probably inserted the records when FilterInterface was a class or before using the discriminators.
Solution:
Dropping the collection and re-populating will work smoothly.
If it's production scenario, you might wanna add the field _t manually to each document.
Tip: Always use the same code for serialization and deserialization.
Explanation:
Referring to the documentation of the c-sharp driver.
The default discriminator conventions both use an element named _t to store the discriminator value in the BSON document.
If you have inserted the documents before enabling the discriminators, there would be no field _t in the document. When the driver starts decoding, it won't find and fallback to default decoder for the interface FilterInterface.
On the other hand, if you have inserted the documents when FilterInterface was a class, the value of _t will be the fully qualified name of the class. When the decoder starts to decode, it will get the ClassModel and try to create an instance of FilterInterface. Since it is now an interface, the decoder won't find the constructor.
Here is some additional info: you can change the field _t to any other name and you can specify the discriminator value by using over the classes.
#BsonDiscriminator(key = "<field_id>", value = "<value>")
Here is the modified version of the example of that answer. Please run it with discriminators disabled and then run it with discriminators enabled. You will face the same error as yours. Then clean the collection and then try again.
package org.bson.codecs.chng;
import com.google.common.collect.Lists;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.ClassModel;
import org.bson.codecs.pojo.PojoCodecProvider;
import org.bson.conversions.Bson;
import java.util.Arrays;
import java.util.List;
public class MongoInterfaceTest {
private static MongoClient mongoClient;
static {
init();
}
public static void init() {
try {
ClassModel<User> userClassModel = ClassModel.builder(User.class).enableDiscriminator(false).build();
ClassModel<JavaUser> javaUserClassModel = ClassModel.builder(JavaUser.class).enableDiscriminator(false).build();
ClassModel<PythonUser> pythonUserClassModel = ClassModel.builder(PythonUser.class).enableDiscriminator(false).build();
ClassModel<TestUser> testUserClassModel = ClassModel.builder(TestUser.class).enableDiscriminator(false).build();
CodecRegistry pojoCodecRegistry = CodecRegistries.fromRegistries(
MongoClientSettings.getDefaultCodecRegistry(),
CodecRegistries.fromProviders(
PojoCodecProvider.builder()
.register(
userClassModel,
javaUserClassModel,
pythonUserClassModel,
testUserClassModel
)
.build()
)
);
mongoClient = MongoClients.create(
MongoClientSettings.builder()
.codecRegistry(pojoCodecRegistry)
.applyConnectionString(new ConnectionString(ApplictaionConfig.MONGODB_URL))
.applyToConnectionPoolSettings(builder -> {
builder.minSize(10);
})
.build()
);
} catch (Exception e) {
System.out.println("Connection mongodb failed");
throw new RuntimeException();
}
}
public static void main(String[] args) {
MongoCollection<TestUser> collection = getMongoCollection("TestUser", TestUser.class);
JavaUser javaUser = new JavaUser<Integer>("a");
PythonUser pythonUser = new PythonUser<String>("b", "1");
TestUser testUser = new TestUser(javaUser.name, javaUser);
insertOne(collection, testUser);
testUser = new TestUser(pythonUser.name, pythonUser);
insertOne(collection, testUser);
Bson bson = Filters.and(Filters.eq("name", "a"));
TestUser testUser1 = findFirst(collection, bson);
System.out.println(testUser1);
testUser1.users.forEach(x -> System.out.println(x.dev()));
bson = Filters.and(Filters.eq("name", "b"));
testUser1 = findFirst(collection, bson);
System.out.println(testUser1);
testUser1.users.forEach(x -> System.out.println(x.dev()));
}
/**
* 获得collection对象
*/
public static <T> MongoCollection<T> getMongoCollection(String collectionName, Class<T> tClass) {
MongoDatabase mongoDatabase = mongoClient.getDatabase("kikuu");
MongoCollection<T> collection = mongoDatabase.getCollection(collectionName, tClass);
return collection;
}
public static <T> void insertOne(MongoCollection<T> collection, T document) {
insertMany(collection, Lists.newArrayList(document));
}
public static <T> void insertMany(MongoCollection<T> collection, List<T> documents) {
collection.insertMany(documents);
}
public static <T> T findFirst(MongoCollection<T> collection) {
return (T) collection.find().first();
}
public static <T> T findFirst(MongoCollection<T> collection, Bson bson) {
return (T) collection.find(bson).first();
}
public static interface User<T> {
String dev();
T foo();
}
public static class JavaUser<T> implements User<T> {
public String name;
public JavaUser() {
}
public JavaUser(String name) {
this.name = name;
}
#Override
public String dev() {
return "java";
}
#Override
public String toString() {
return "JavaUser{" +
"name='" + name + '\'' +
'}';
}
#Override
public T foo() {
return null;
}
}
public static class PythonUser<T> implements User<T> {
public String name;
public String age;
public PythonUser() {
}
public PythonUser(String name, String age) {
this.name = name;
this.age = age;
}
#Override
public String dev() {
return "python";
}
#Override
public String toString() {
return "PythonUser{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
#Override
public T foo() {
return null;
}
}
public static class TestUser {
public String name;
public List<User> users;
public TestUser() {
}
public TestUser(String name, User... users) {
this.name = name;
this.users = Arrays.asList(users);
}
#Override
public String toString() {
return "TestUser{" +
"name='" + name + '\'' +
", user=" + users +
'}';
}
}
}
I have a problem while deserializing an XML file.
My file is like:
<mission>
<branch>
<alternative uid="0" type="ALT_MONITOR"/>
<alternative uid="1" type="ALT_IF" condition="i==10"/>
</branch>
</mission>
I have a class called Alternative:
public abtract class Alternative {
#XStreamAsAttribute
public int uid;
#XStreamAsAttribute
public String type;
}
This class is extended by two other class:
#XStreamAlias("alternative")
public class AlternativeA extends Alternative {
}
#XStreamAlias("alternative")
public class AlternativeB extends Alternative {
#XStreamAsAttribute
public String condition;
}
And then i have an xStream converter :
public class AlternativeConverter extends ReflectionConverter {
public AlternativesConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
}
#Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
if (reader.getAttribute("condition") != null) {
AlternativeA alternativeA = new AlternativeA();
alternativeA.setUid(Integer.parseInt(reader.getAttribute("uid")));
alternativeA.setCondition(reader.getAttribute("condition"));
return super.doUnmarshal(alternativeA, reader, context);
}else {
AlternativeB alternativeB = new AlternativeB();
alternativeB.setUid(Integer.parseInt(reader.getAttribute("uid")));
return super.doUnmarshal(alternativeB, reader, context);
}
}
#SuppressWarnings("unchecked")
#Override
public boolean canConvert(Class clazz) {
return Alternative.class.isAssignableFrom(clazz);
}
}
But when i try to convert the xml to an object. When it reaches the alternative with a condition it throws an exception :
Cannot convert type AlternativeB to type AlternativeA
Do any of you have an idea or an int on what could cause that error ?
Thank you in advance.
Java:
package de.mosst.spielwiese;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.mapper.Mapper;
import lombok.Data;
public class XStreamMultiClassesTest {
#Test
public void smokeTest() {
InputStream file = XStreamMultiClassesTest.class.getResourceAsStream("XStreamMultiClassesTest.xml");
XStream xStream = new XStream();
xStream.ignoreUnknownElements();
xStream.processAnnotations(Mission.class);
xStream.processAnnotations(Alternative.class);
Converter converter = new AlternativeConverter(xStream.getMapper(), xStream.getReflectionProvider());
xStream.registerConverter(converter);
Mission mission = (Mission) xStream.fromXML(file);
System.out.println(mission);
mission.branch.forEach(a -> {
System.out.println(a.getClass());
if (a instanceof AlternativeA) {
System.out.println("- condition: " + ((AlternativeA) a).condition);
}
});
}
public class AlternativeConverter extends ReflectionConverter {
public AlternativeConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
}
#Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Alternative alternative = null;
if (reader.getAttribute("condition") != null) {
alternative = new AlternativeA();
((AlternativeA) alternative).condition = reader.getAttribute("condition");
} else {
alternative = new AlternativeB();
}
alternative.uid = Integer.parseInt(reader.getAttribute("uid"));
return super.doUnmarshal(alternative, reader, context);
}
#Override
public boolean canConvert(#SuppressWarnings("rawtypes") Class clazz) {
return Alternative.class.isAssignableFrom(clazz);
}
}
#XStreamAlias("mission")
#Data
class Mission {
public List<Alternative> branch = new ArrayList<>();
}
#XStreamAlias("alternative")
#Data
abstract class Alternative {
#XStreamAsAttribute
public int uid;
#XStreamAsAttribute
public String type;
}
class AlternativeA extends Alternative {
public String condition;
}
class AlternativeB extends Alternative {
}
}
XML:
<?xml version="1.0" encoding="UTF-8"?>
<mission>
<branch>
<alternative uid="0" type="ALT_MONITOR" />
<alternative uid="1" type="ALT_IF" condition="i==10" />
</branch>
</mission>
I have the following Enum Class:
#XmlJavaTypeAdapter(value = PastMedicalHistoryAdapter.class)
public enum PastMedicalHistory {
Diabetes, Obesity, Smoking, COPD, CAD, PVD, Other
}
and Generic Adapter:
public abstract class GenericEnumAdapter<T extends Enum> extends XmlAdapter<String, Enum> {
#Override
public Enum unmarshal(String v) throws Exception {
log.info("unmarshal: {}", v);
return convert(v + "");
}
public abstract T convert(String value);
#Override
public String marshal(Enum v) throws Exception {
log.info("marshal: {}", v.name());
String s = "{\"" + v.name() + "\":" + true + "}";
return s;
}
}
and basic implementation with
public class PastMedicalHistoryAdapter extends GenericEnumAdapter<PastMedicalHistory> {
#Override
public PastMedicalHistory convert(String value) {
return PastMedicalHistory.valueOf(value);
}
}
and I used it like this:
#Data
#XmlRootElement(name = "Patient")
public class Test {
private List<PastMedicalHistory> history;
public static void main(String[] args) throws Exception {
JAXBContext cxt = JAXBContext.newInstance(Test.class);
Marshaller mar = cxt.createMarshaller();
mar.setProperty(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
mar.setProperty(JAXBContextProperties.MEDIA_TYPE, "application/json");
mar.setProperty(JAXBContextProperties.JSON_INCLUDE_ROOT, Boolean.FALSE);
Test t = new Test();
t.setHistory(Arrays.asList(PastMedicalHistory.CAD, PastMedicalHistory.Diabetes));
mar.marshal(t, System.out);
}
}
the problem, is Output for history is always null like this:
[exec:exec]
2013-09-29 12:13:18:511 INFO marshal: CAD
2013-09-29 12:13:18:522 INFO marshal: Diabetes
{
"history" : [ null, null ]
}
I'm using Moxy 2.5.1 as JAXB Provider, so What I'm missing, or what I'm doing wrong?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
I have been able to reproduce the bug that you are hitting. You can use the following link to track our progress on this issue:
http://bugs.eclipse.org/418486
I am using org.simpleframework.xml (http://simple.sourceforge.net/) to serialize Java Objects to XML.
What I would like to add is to add a comments area in the resulting XML, based on Annotations in the Java object.
So for example I would like to write some Java Object like:
#Root(name = "myclass")
public class MyClass {
#Element(required=true)
#Version(revision=1.1)
#Comment(text=This Element is new since, version 1.1, it is a MD5 encrypted value)
private String activateHash;
}
And the resulting xml would look like:
<myclass version="1.1">
<!-- This Element is new since, version 1.1, it is a MD5 encrypted value -->
<activateHash>129831923131s3jjs3s3jjk93jk1</activateHash>
</myclass>
There is an example in their docs on howto write a Visitor that will write a comments in the xml:
http://simple.sourceforge.net/download/stream/doc/tutorial/tutorial.php#intercept
However: How can I attach a Visitor to a Strategy at all?
And further the Visitor concept of simpleframework does not allow access to the raw parsing class.
In the Visitor there is only a method to overwrite:
public void write(Type type, NodeMap<OutputNode> node) { ... }
=> OutputNode does not give me a chance to read the Annotation of the Element that I am parsing. So how should one access the Annotations of the attribute.
Thanks!
Sebastian
Update as of 2012-11-05:
Answer by the author of org.simpleframework.xml:
This works
https://simple.svn.sourceforge.net/svnroot/simple/trunk/download/stream/src/test/java/org/simpleframework/xml/strategy/CommentTest.java
package org.simpleframework.xml.strategy;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.simpleframework.xml.Default;
import org.simpleframework.xml.Root;
import org.simpleframework.xml.ValidationTestCase;
import org.simpleframework.xml.core.Persister;
import org.simpleframework.xml.stream.InputNode;
import org.simpleframework.xml.stream.NodeMap;
import org.simpleframework.xml.stream.OutputNode;
public class CommentTest extends ValidationTestCase {
#Retention(RetentionPolicy.RUNTIME)
private static #interface Comment {
public String value();
}
#Root
#Default
private static class CommentExample {
#Comment("This represents the name value")
private String name;
#Comment("This is a value to be used")
private String value;
#Comment("Yet another comment")
private Double price;
}
private static class CommentVisitor implements Visitor {
public void read(Type type, NodeMap<InputNode> node) throws Exception {}
public void write(Type type, NodeMap<OutputNode> node) throws Exception {
if(!node.getNode().isRoot()) {
Comment comment = type.getAnnotation(Comment.class);
if(comment != null) {
node.getNode().setComment(comment.value());
}
}
}
}
public void testComment() throws Exception {
Visitor visitor = new CommentVisitor();
Strategy strategy = new VisitorStrategy(visitor);
Persister persister = new Persister(strategy);
CommentExample example = new CommentExample();
example.name = "Some Name";
example.value = "A value to use";
example.price = 9.99;
persister.write(example, System.out);
}
}
Update as of 2012-11-01 20:16
this is the workaround that seems to get the desired effect - the necessary FieldHelper is described in (Get the value of a field, given the hierarchical path)
/**
* write according to this visitor
*/
public void write(Type type, NodeMap<OutputNode> node) {
OutputNode element = node.getNode();
Class ctype = type.getType();
String comment = ctype.getName();
if (!element.isRoot()) {
FieldHelper fh = new FieldHelper();
element.setComment(comment);
try {
if (type.getClass().getSimpleName().startsWith("Override")) {
type = (Type) fh.getFieldValue(type, "type");
}
if (type.getClass().getSimpleName().startsWith("Field")) {
Field field = (Field) fh.getFieldValue(type, "field");
System.out.println(field.getName());
Comment commentAnnotation = field.getAnnotation(Comment.class);
if (commentAnnotation != null) {
element.setComment(commentAnnotation.value());
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Here is how far I got with this. Unfortunately it does not work as expected. I have written an E-Mail to the author of the Simpleframwork for XML.
/**
* write according to this visitor
*/
public void write(Type type, NodeMap<OutputNode> node) {
OutputNode element = node.getNode();
Class ctype = type.getType();
String comment = ctype.getName();
if (!element.isRoot()) {
Comment commentAnnotation = type.getAnnotation(Comment.class);
if (commentAnnotation!=null)
element.setComment(commentAnnotation.value());
else
element.setComment(comment);
}
}
#Override
public void read(Type type, NodeMap<InputNode> nodeMap) throws Exception {
}
}
I declared the Comment annotation like this:
package com.bitplan.storage.simplexml;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Comment {
String value();
}
which is then usable like this:
#Comment("this is the unique identifier")
private long id;
adding the Visitor was possible like this:
/**
* get Serializer
*
* #return
*/
public Serializer getSerializer() {
Serializer serializer = null;
Strategy strategy=null;
VisitorStrategy vstrategy=null;
if ((idname != null) && (refname != null)) {
strategy = new CycleStrategy(idname, refname);
}
CommentVisitor cv=new CommentVisitor();
if (strategy==null) {
vstrategy=new VisitorStrategy(cv);
} else {
vstrategy=new VisitorStrategy(cv,strategy);
}
serializer = new Persister(vstrategy);
return serializer;
}