I've got the following xsd tag:
<xs:complexType name="documentation">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="language" use="required"/>
</xs:extension>
</xs:simpleContent>
this generates (with jax-b):
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "documentation", propOrder = {
"value"
})
public class Documentation {
#XmlValue
protected String value;
#XmlAttribute(name = "language", required = true)
protected String language;
And I want some output like:
<documentation language="NL">SomeValue</documentation>
but Xstream generates:
<documentation language="NL">
<value>SomeValue</value>
</documentation>
how could I remove the value tags? I don't want them..
Code to generate the xml tags (this is just a snippet..):
private void createDocumentation(Description description, String docNL) {
List<Documentation> documentations = description.getDocumentation();
Documentation documentationNL = new Documentation();
documentationNL.setLanguage("NL");
documentationNL.setValue(docNL);
documentations.add(documentationNL);
}
private void createXmlFile(Description description) {
XStream xstream = new XStream(new DomDriver());
xstream.alias("description", Description.class);
xstream.alias("documentation", Documentation.class);
xstream.addImplicitCollection(Description.class, "documentation");
xstream.useAttributeFor(Documentation.class, "language");
String xml = xstream.toXML(description);
}
XStream provides a standard converter implementation called ToAttributedValueConverter that you can wire in for any simple-content-plus-attributes type like this:
#XStreamConverter(value = ToAttributedValueConverter.class, strings = { "value" })
public class Documentation {
protected String value;
protected String language;
}
The strings annotation element names the property that corresponds to the element content, all other properties will become attributes. If you want to declare the converter using xstream.registerConverter instead of using XStream annotations then you use
xstream.registerConverter(new ToAttributedValueConverter(Documentation.class,
xstream.getMapper(), xstream.getReflectionProvider(), xstream.getConverterLookup(),
"value"));
(the Mapper, ReflectionProvider and ConverterLookup objects get supplied to the converter automatically when you register it using annotations, but must be provided explicitly for registerConverter).
One option is to create a custom converter for your Documentation object.
Take a look at the XStream Converter tutorial
EDIT TS:
adding:
xstream.registerConverter(new DocumentationConverter());
and
public class DocumentationConverter implements Converter {
public boolean canConvert(Class clazz) {
return clazz.equals(Documentation.class);
}
public void marshal(Object value, HierarchicalStreamWriter writer,
MarshallingContext context) {
Documentation documentation = (Documentation) value;
writer.addAttribute("language", documentation.getLanguage());
writer.setValue(documentation.getValue());
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Documentation documentation = new Documentation();
reader.moveDown();
documentation.setLanguage(reader.getAttribute("language"));
documentation.setValue(reader.getValue());
reader.moveUp();
return documentation;
}
}
did the job
Related
I have data structures generated by jaxb. Parts of the structures are basically identical but they are in different namespaces and therefore the generated Java types are different.
I need to transfer data between these structures. In the project ModelMapper is used for mapping so I am expected to use that.
My problem is that ModelMapper can't map the lists generated for 'maxOccurs="unbounded"' elements.
Let's say I have the following schema:
<xs:complexType name="CityData">
<xs:sequence>
<xs:element name="districtData" type="DistrictData" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="DistrictData">
<xs:sequence>
<xs:element name="population" type="xs:int" nillable="false" minOccurs="1" maxOccurs="1"/>
</xs:sequence>
</xs:complexType>
I have this schema in namespacea and in namespaceb so
Jaxb generates the following types into package namespacea and into package namespaceb:
public class CityData {
#XmlElement(required = true)
protected List<DistrictData> districtData;
//... jaxb explanation why there's no setter
public List<DistrictData> getDistrictData() {
if (districtData == null) {
districtData = new ArrayList<DistrictData>();
}
return this.districtData;
}
}
public class DistrictData {
protected int population;
public int getPopulation() {
return population;
}
public void setPopulation(int value) {
this.population = value;
}
}
Now If I create a source CityData from package namespacea and ask modelmapper to map it to a destination CityData in namespaceb then the data is not mapped:
CityData cityData = new CityData();
DistrictData districtData = new DistrictData();
districtData.setPopulation(1234);
cityData.getDistrictData().add(districtData);
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
namespaceb.CityData dest = modelMapper.map(cityData, namespaceb.CityData.class);
System.out.println("dest.districtData: " + dest.getDistrictData());
result is:
dest.districtData: []
In other words, districtData is not copied to destination.
I understand that ModelMapper does not find a setter for districtData and therefore not map it. I read that one can reconfigure Jaxb to generate setters for list properties, but the jaxb object generation is not in my hand in the project.
So I would like to find out if there is a nice solution for the mapping with ModelMapper, or maybe with other mapper library in these cases.
I've created a mini project: https://github.com/riskop/ModelMapperJaxb
I think that you just need to enable FieldMatching and set the access level of the fields to match to handle the missing setter. Check this configuration:
modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT)
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(AccessLevel.PROTECTED);
Javadoc:
setFieldAccessEnabled
Sets whether field matching should be enabled. When true, mapping may take place between accessible fields. Default is false.
setFieldAccessLevel
Indicates that fields should be eligible for matching at the given accessLevel.
Note: Field access is only used when field matching is enabled.
I had a rough idea of a clumsy workaround with ModelMapper.Converter facility before reading pirho's answer. I think that pirho's answer is better (accepted) but for the record, below is the Converter workaround. This is basically manually defining the conversion for the substructures where there is no setter:
CountryData countryData = new CountryData();
CityData cityData = new CityData();
DistrictData districtData = new DistrictData();
districtData.setPopulation(1234);
cityData.getDistrictData().add(districtData);
countryData.getCityData().add(cityData);
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
modelMapper.addConverter(new Converter<CountryData, namespaceb.CountryData>() {
#Override
public namespaceb.CountryData convert(MappingContext<CountryData, namespaceb.CountryData> context) {
namespaceb.CountryData result = new namespaceb.CountryData();
if(context.getSource() != null) {
for(CityData cityData : context.getSource().getCityData()) {
namespaceb.CityData mapped = modelMapper.map(cityData, namespaceb.CityData.class);
result.getCityData().add(mapped);
}
}
return result;
}
});
modelMapper.addConverter(new Converter<CityData, namespaceb.CityData>() {
#Override
public namespaceb.CityData convert(MappingContext<CityData, namespaceb.CityData> context) {
namespaceb.CityData result = new namespaceb.CityData();
if(context.getSource() != null) {
for(DistrictData districtData : context.getSource().getDistrictData()) {
namespaceb.DistrictData mapped = modelMapper.map(districtData, namespaceb.DistrictData.class);
result.getDistrictData().add(mapped);
}
}
return result;
}
});
namespaceb.CountryData destCountryData = modelMapper.map(countryData, namespaceb.CountryData.class);
assertEquals(1, destCountryData.getCityData().size());
namespaceb.CityData destCityData = destCountryData.getCityData().get(0);
assertEquals(1, destCityData.getDistrictData().size());
namespaceb.DistrictData destDistrictData = destCityData.getDistrictData().get(0);
assertEquals(1234, destDistrictData.getPopulation());
I have an empty tag like this <tagName/>. When I unmarshalling it if this property is the type of long or float it is null. But if this property is the type of string, the property is tagName = '';. And after marshalling is <tagName></tagName>. How can I set empty tag name which is string java property to null while unmarshalling?
There are (at least) 2 ways to do this.
If the classes are yourself and not auto-generated from xsd or similar you can use an adapter.
For example a class Cart:
#XmlRootElement(name = "Cart")
#XmlAccessorType(XmlAccessType.FIELD)
public class Cart {
#XmlJavaTypeAdapter(EmptyTagAdapter.class)
protected String tagName;
}
can use an adapter like below:
public class EmptyTagAdapter extends XmlAdapter<String, String> {
#Override
public String marshal(String arg0) throws Exception {
return arg0;
}
#Override
public String unmarshal(String arg0) throws Exception {
if(arg0.isEmpty()) {
return null;
}
return arg0;
}
}
For an xml that looks like this:
<Cart>
<tagName/>
</Cart>
You would get the empty tagName as null.
If your classes are generated from an xsd you could mention that the field can be nillable.
For example as below:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.1">
<xs:element name="Cart">
<xs:complexType>
<xs:all>
<xs:element name="tagName" type="xs:string" nillable="true" />
</xs:all>
</xs:complexType>
</xs:element>
</xs:schema>
and then you would need to have in your xml with the empty element xsi:nil="true" as this example:
<Cart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<tagName/>
<tagName xsi:nil="true" />
</Cart>
It would have the same result, the value as null.
The use of the adapter is more to my liking but depends on your case. Hopefully one of the cases covers you.
I want to convert uid attribute's value in MyJaxbModel class to uppercase during UnMarshalling. I did write UpperCaseAdapter that does the work for me. However with this approach, application performance is deteriorated to unacceptable level (as there are thousands of XML files unmarshalled to MyJaxbModel). I cannot use String.toUppperCase() in getter /setter as these JAXB models are auto generated from XSD and I dont want to tweak them.
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "myJaxbModel")
public class MyJaxbModel
{
protected String name;
#XmlJavaTypeAdapter(UpperCaseAdapter.class)
protected String uid;
// getters and setters
}
public class UpperCaseAdapter extends XmlAdapter<String, String>
{
#Override
public String unmarshal( String value ) throws Exception
{
return value.toUpperCase();
}
#Override
public String marshal( String value ) throws Exception
{
return value;
}
}
<!--My XSD makes use of below xjc:javaType definition to auto-configure this-->
<xsd:simpleType name="uidType">
<xsd:annotation>
<xsd:appinfo>
<xjc:javaType name="java.lang.String"
adapter="jaxb.UpperCaseAdapter" />
</xsd:appinfo>
</xsd:annotation>
<xsd:restriction base="xsd:string" />
</xsd:simpleType>
Expected Input:
<myJaxbModel name="abc" uid="xyz" />
Expected Output: myJaxbModel.toString() -> MyJaxbModel[name=abc, uid=XYZ]
Is there a better approach to achieve desired results?
Why didn't you simply parse it to upper case in the getUid() or while setting?
if (uid != null){
return uid.toUpperCase();
}
...
or
...
if (uid != null){
this.uir = uid.toUpperCase();
}
I think that is the easier and cleanest way to do it...
In the below format my doubt is the type mentioned with every field. Can you please suggest some solution? This is a requirement from the third party who will be consuming this.
subject":{
"type":"string",
"$":"Cabinet model number?"
}
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Below is how this can be done using MOXy's JSON-binding.
Domain Model (Root)
The #XmlElement annotation can be used to specify the type of the property. Setting the type to be Object will force a type qualified to be written out.
import javax.xml.bind.annotation.*;
public class Root {
private String subject;
#XmlElement(type=Object.class)
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
}
Demo
Since a type qualifier will be marshalled out a key will need to be written for the value. By default this will be value. You can use the JSON_VALUE_WRAPPER property to change this to $.
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(3);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
properties.put(JAXBContextProperties.JSON_VALUE_WRAPPER, "$");
JAXBContext jc = JAXBContext.newInstance(new Class[] {Root.class}, properties);
Root root = new Root();
root.setSubject("Cabinet model number?");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output
Below is the output from running the demo code.
{
"subject" : {
"type" : "string",
"$" : "Cabinet model number?"
}
}
For More Information
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
I have done this using gson API from google. Wrote a custom serializer which checks the type and value and creates the JSON object based on that.
Can't I register a bunch of XmlAdapters to Marshaller|Unmarshaller so that I wouldn't need to specify #XmlJavaTypeAdapter on each filed, whose type isn't natively JAXB-supported?
I find it somewhat redundant.
BTW, someMarshaller.setAdapter(...) seem not to do anything.
This is a quite a good question !
The short answer is that no, using setAdapter on marshaller / unmarshaller does not mean that you don't have to use #XmlJavaTypeAdapter.
Let me explain this with a hypothetical (yet valid!) scenario.
Consider in a web application, one fetches an event in the form of xml having following schema:
<xs:element name="event" >
<xs:complexType>
<xs:sequence>
<!-- Avoiding other elements for concentrating on our adapter -->
<xs:element name="performedBy" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
Equivalent to this, your model will look like:
#XmlRootElement(name="event")
#XmlType(name="")
public class Event {
#XmlElement(required=true)
protected String performedBy;
}
Now the application is already having a bean called User which maintains the detailed
information about the user.
public class User {
private String id;
private String firstName;
private String lastName;
..
}
Note that this User is not known to your JAXB Context.
For simplicity we have User as POJO, but it can be any Valid Java Class.
What application architect want is Event's performedBy should be represented as User
to gain full details.
Here is where #XmlJavaTypeAdapter comes into picture
JAXBContext is aware about performedBy as xs:string , but it has to be represented as
User in memory in Java.
Modified model looks like:
#XmlRootElement(name="event")
#XmlType(name="")
public class Event {
#XmlElement(required=true)
#XmlJavaTypeAdapter(UserAdapter.class)
protected User performedBy;
}
UserAdapter.java:
public class UserAdapter extends XmlAdapter<String, User> {
public String marshal(User boundType) throws Exception {
..
}
public User unmarshal(String valueType) throws Exception {
..
}
}
The Adapter's definition says that -
BoundType is User (In Memeory representation)
ValueType is String (The data type JAXB Context is aware of)
Coming to back to your question -
I find it somewhat redundant.
BTW, someMarshaller.setAdapter(...) seem not to do anything.
Consider that our Adapter requires a class called UserContext in order to marshal / unmarshal sucessfully.
public class UserAdapter extends XmlAdapter<String, User> {
private UserContext userContext;
public String marshal(User boundType) throws Exception {
return boundType.getId();
}
public User unmarshal(String valueType) throws Exception {
return userContext.findUserById(valueType);
}
}
Now the question is how will UserAdapter will fetch an instace of UserContext ??
As a good design one should always supply it while it has got instantiated ..
public class UserAdapter extends XmlAdapter<String, User> {
private UserContext userContext;
public UserAdapter(UserContext userContext) {
this.userContext = userContext;
}
public String marshal(User boundType) throws Exception {
return boundType.getId();
}
public User unmarshal(String valueType) throws Exception {
return userContext.findUserById(valueType);
}
}
But JAXB Runtime can only accept Adapter with No-args constructor ..
(Obviously JAXBContext does not know about application specific model)
So thankfully there is an option :D
You can tell your unmarshaller to use given instance of UserAdapter rather than instating it by own its own.
public class Test {
public static void main(String... args) {
JAXBContext context = JAXBContext.getInstance(Event.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
UserContext userContext = null; // fetch it from some where
unmarshaller.setAdapter(UserAdapter.class, new UserAdapter(userContext));
Event event = (Event) unmarshaller.unmarshal(..);
}
}
setAdapter method is available on both Marshaller & Unmarshaller
Note:
setAdapter on marshaller / unmarshaller does not mean that you don't have to use #XmlJavaTypeAdapter.
#XmlRootElement(name="event")
#XmlType(name="")
public class Event {
#XmlElement(required=true)
// #XmlJavaTypeAdapter(UserAdapter.class)
protected User performedBy;
}
If you omit this JAXB runtime has no clue that User is your Bound Type & Value Type is something else. It will try to marshal User as is & u will end up having wrong xml
(or validation failure if enabled)
While we have taken a scenario where Adapter is required to be with arguments, hence
use setAdapter method.
Some adavanced usages are also there, where in even if you have default no-arg constructor, yet you provide an instance of the Adapter
May this adapter is configured with data, which marshal / unmarshal operation is using !
You can use the package-info.java
That's called "package level".
Example :
put a package-info.java in the same package that the class you want to marshall/unmarshall.
Suppose you have a class mypackage.model.A and an adapter CalendarAdapter in mypackage.adapter.
Declare a package-info.java file in mypackage.model containing :
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(value = CalendarAdapter.class, type = Calendar.class)
})
package mypackage.model;
import java.util.Calendar;
import mypackage.adapter.CalendarAdapter;
All field of type Calendar in the class A will be marshalled or unmarshalled with the CalendarAdapter.
You can find useful informations there :
http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html