I'm struggling with some JAXB parsing and need some guidance.
Essentially, I'm trying to add attributes to my class variables that I have already declared as Elements using #XmlElement. So far, any attempt to use #XmlAttribute sets the attribute at the class level.
What I"m currently getting is this:
<DataClass newAttribute="test">
<myElement>I wish this element had an attribute</myElement>
<anotherElement>I wish this element had an attribute too</anotherElement>
</DataClass>
I'd like to to do this:
<DataClass>
<myElement thisAtt="this is what I'm talking about">This is better</myElement>
<anotherElement thisAtt="a different attribute here">So is this</anotherElement>
</DataClass>
I've seen other posts add an attribute to a single element using the #XmlValue, but that doesn't work when you have Elements, and won't work on multiple elements.
Does anyone have a thought on how this could be accomplished?
Thanks!
Jason
This will create that XML:
public class JaxbAttributes {
public static void main(String[] args) throws Exception {
Marshaller marshaller =
JAXBContext.newInstance(DataClass.class).createMarshaller();
StringWriter stringWriter = new StringWriter();
DataClass dataClass = new DataClass(
new Foo("this is what I'm talking about", "This is better"),
new Foo("a different attribute here", "So is this"));
marshaller.marshal(dataClass, stringWriter);
System.out.println(stringWriter);
}
#XmlRootElement(name = "DataClass")
#XmlType(propOrder = {"myElement", "anotherElement"})
static class DataClass {
private Foo myElement;
private Foo anotherElement;
DataClass() {}
public DataClass(Foo myElement, Foo anotherElement) {
this.myElement = myElement;
this.anotherElement = anotherElement;
}
public Foo getMyElement() { return myElement; }
public void setMyElement(Foo myElement) { this.myElement = myElement; }
public Foo getAnotherElement() { return anotherElement; }
public void setAnotherElement(Foo anotherElement) { this.anotherElement = anotherElement; }
}
static class Foo {
private String thisAtt;
private String value;
Foo() {}
public Foo(String thisAtt, String value) {
this.thisAtt = thisAtt;
this.value = value;
}
#XmlAttribute
public String getThisAtt() { return thisAtt; }
public void setThisAtt(String thisAtt) { this.thisAtt = thisAtt; }
#XmlValue
public String getValue() { return value; }
public void setValue(String value) { this.value = value; }
}
}
Note: I'm the EclipseLink JAXB (MOXy) lead, and a member of the JAXB 2.X (JSR-222) expert group.
Alternatively you could use the #XmlPath extension in MOXy to handle this use case:
DataClass
The #XmlPath annotation can be used with the standard JAXB annotations:
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement(name="DataClass")
#XmlType(propOrder={"myElement", "anotherElement"})
public class DataClass {
private String myElement;
private String myElementThisAtt;
private String anotherElement;
private String anotherElementThisAtt;
public String getMyElement() {
return myElement;
}
public void setMyElement(String myElement) {
this.myElement = myElement;
}
#XmlPath("myElement/#thisAtt")
public String getMyElementThisAtt() {
return myElementThisAtt;
}
public void setMyElementThisAtt(String myElementThisAtt) {
this.myElementThisAtt = myElementThisAtt;
}
public String getAnotherElement() {
return anotherElement;
}
public void setAnotherElement(String anotherElement) {
this.anotherElement = anotherElement;
}
#XmlPath("anotherElement/#thisAtt")
public String getAnotherElementThisAtt() {
return anotherElementThisAtt;
}
public void setAnotherElementThisAtt(String anotherElementThisAtt) {
this.anotherElementThisAtt = anotherElementThisAtt;
}
}
Demo
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(DataClass.class);
DataClass dataClass = new DataClass();
dataClass.setMyElement("This is better");
dataClass.setMyElementThisAtt("this is what I'm talking about");
dataClass.setAnotherElement("So is this");
dataClass.setAnotherElementThisAtt("a different attribute here");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(dataClass, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<DataClass>
<myElement thisAtt="this is what I'm talking about">This is better</myElement>
<anotherElement thisAtt="a different attribute here">So is this</anotherElement>
</DataClass>
More Information
http://bdoughan.blogspot.com/2010/07/xpath-based-mapping.html
http://bdoughan.blogspot.com/2011/05/specifying-eclipselink-moxy-as-your.html
Related
I have a java model class that I need to unmarshall to xml. I am using jaxb. The problem I am facing is that I need empty elements to appear in the output, but by default it seems they do not. Here is sample of my model (with many fewer fields):
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "model:MyModel")
public class MyModel {
private String field1;
private String field2;
private String field3;
public MyModel() {}
public String getField1() {
return field1;
}
#XmlElement(name = "model:field1")
public void setField1(String field1) {
this.field1 = field1;
}
public String getField2() {
return field2;
}
#XmlElement(name = "model:field2")
public void setField2(String field2) {
this.field2 = field2;
}
public String getField3() {
return field3;
}
#XmlElement(name = "model:field3")
public void setField3(String field3) {
this.field3 = field3;
}
}
, and for example purposes, I simple app like this:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
public class MyApplication {
public static void main(String[] args) {
MyModel myModel = new MyModel();
myModel.setField1("cat");
myModel.setField3("dog");
JAXBContext jc = null;
try {
jc = JAXBContext.newInstance(MyModel.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(myModel, System.out);
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
As written, the output is:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model:MyModel>
<model:field1>cat</model:field1>
<model:field3>dog</model:field3>
</model:MyModel>
, but I need the empty element for the null field2. Also, my model actually has a couple of dozen fields, a combination of Strings, Longs, and Integers, any one of which can be null.
So I am looking for a solution that provides empty element for any of these fields. I'd also consider alternatives to JAXB if anyone has alternatives that will support what I need. I would be grateful for any ideas. Thank you
I decided to use Jackson for xml rather than JAXB. It, by default, handled my base situation (requiring empty elements for null fields). But also, Jackson for xml essentially is just Jackson for JSON with a few additional annotations and other capabilties, so it is more portable between JSON and XML.
So, here is sample model:
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonRootName("model:MyJacksonModel")
public class MyJacksonModel {
#JsonProperty("model:field1")
private String field1;
#JsonProperty("model:field2")
private String field2;
#JsonProperty("model:field3")
private String field3;
public MyJacksonModel() { }
public String getField1() { return field1;}
public void setField1(String field1) { this.field1 = field1;}
public String getField2() { return field2;}
public void setField2(String field2) { this.field2 = field2; }
public String getField3() { return field3;}
public void setField3(String field3) { this.field3 = field3; }
}
, and simple app:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import mil.dia.mars.de.model.MyJacksonModel;
public class MyApplication {
public static void main(String[] args) {
MyJacksonModel myJModel = new MyJacksonModel();
myJModel.setField1("cat");
myJModel.setField3("dog");
XmlMapper xmlMapper = new XmlMapper();
String jString = "";
try {
jString = xmlMapper.writeValueAsString(myJModel);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
System.out.println(jString);
}
}
, and output:
<model:MyJacksonModel>
<model:field1>cat</model:field1>
<model:field2/>
<model:field3>dog</model:field3>
</model:MyJacksonModel>
I'm using JAXB for creating xml. I want to set attribute 'lang' on elements PrimaryValue and AlternativeSpelling.
<AgencyOrUnit>
<PrimaryValue lang="el">ΓΑΔΑ</PrimaryValue>
<AlternativeSpelling lang="en">Athens General Police Directorate</AlternativeSpelling>
</AgencyOrUnit>
Here's my code:
#XmlRootElement(name = "OwnerReference")
#XmlType(propOrder = { "primaryValue", "alternativeSpelling"})
public class AgencyOrUnit {
private String PrimaryValue;
private String AlternativeSpelling;
public String getPrimaryValue() {
return PrimaryValue;
}
public void setPrimaryValue(String PrimaryValue){
this.PrimaryValue = PrimaryValue;
}
public String getAlternativeSpelling() {
return AlternativeSpelling;
}
public void setAlternativeSpelling(String AlternativeSpelling){
this.AlternativeSpelling = AlternativeSpelling;
}
}
Here's process of marshalling:
AgencyOrUnit agencyOrUnit = new AgencyOrUnit();
agencyOrUnit.setPrimaryValue("ΓΑΔΑ");
agencyOrUnit.setAlternativeSpelling("General Police");
The problem is that I don't know how to set property with value on elements primaryValue and alternativeSpelling?
You can use annotations #XmlValue & #XmlAttribute but you need to create a new class to hold both lang and the original value string. Something like this:
#Setter
#AllArgsConstructor
public class LocaleString {
private String lang;
private String value;
#XmlAttribute
public String getLang() {
return lang;
}
#XmlValue
public String getValue() {
return value;
}
}
Then modify your AgencyOrUnit accordingly:
#XmlRootElement(name = "OwnerReference")
#XmlType(propOrder = { "primaryValue", "alternativeSpelling"})
#Getter #Setter
public class AgencyOrUnit {
private LocaleString PrimaryValue;
private LocaleString AlternativeSpelling;
}
Test it:
#Test
void test() throws JAXBException {
AgencyOrUnit agencyOrUnit = new AgencyOrUnit();
agencyOrUnit.setPrimaryValue(new LocaleString("el", "ΓΑΔΑ"));
agencyOrUnit.setAlternativeSpelling(new LocaleString("en", "General Police"));
JAXBContext ctx = JAXBContext.newInstance(AgencyOrUnit.class);
Marshaller marshaller = ctx.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(agencyOrUnit, System.out);
}
and you should see this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OwnerReference>
<primaryValue lang="el">ΓΑΔΑ</primaryValue>
<alternativeSpelling lang="en">General Police</alternativeSpelling>
</OwnerReference>
I've spent a long time trying to figure out how to validate a XML file, and I've got it working with a pre-generated XSD schema. However, I wish to dynamically generate a schema without creating a file, based on the annotation class I have specified, I've tried to not specify any parameters to SchemaFactory, but then it seems to just create an empty schema (see comments below).
Here are the two classes I use for my JAXB reading and writing of a XML file.
This is the code for the class XMLTranslationWrapper:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(namespace = "my.package.namespace")
public class XMLTranslationWrapper {
#XmlElementWrapper(name = "TRANSLATIONS")
#XmlElement(name = "TRANSL")
public ArrayList<XMLTranslationNode> translations;
public XMLTranslationWrapper(){
translations = new ArrayList<XMLTranslationNode>();
}
public void setTranslations(ArrayList<XMLTranslationNode> translations){
this.translations = translations;
}
public XMLTranslationNode getTranslation(String code){
for(XMLTranslationNode transl : translations){
if(transl.getCode().equals(code))
return transl;
}
return null;
}
public void addTranslation(XMLTranslationNode translation){
this.translations.add(translation);
}
}
This is the code for the class XMLTranslationNode:
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlRootElement(name = "TRANSL")
#XmlType(propOrder = { "severity", "translation" })
public class XMLTranslationNode {
private String severity;
private String code;
private String translation;
#XmlElement(name="SEVERITY")
public String getSeverity(){
return this.severity;
}
public void setSeverity(String severity){
this.severity = severity;
}
#XmlAttribute(name="CODE")
public String getCode(){
return this.code;
}
public void setCode(String code){
this.code = code;
}
#XmlElement(name="TRANSLATION")
public String getTranslation(){
return this.translation;
}
public void setTranslation(String translation){
this.translation = translation;
}
}
This is the code I used to generate the pre-generated XSD schema:
public class generateSchema {
public static void main(String[] args) {
JAXBContext jaxbContext;
try {
jaxbContext = JAXBContext.newInstance(XMLTranslationWrapper.class);
SchemaOutputResolver sor = new MySchemaOutputResolver();
jaxbContext.generateSchema(sor);
} catch (JAXBException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
static class MySchemaOutputResolver extends SchemaOutputResolver {
public Result createOutput(String namespaceURI, String suggestedFileName) throws IOException {
File file = new File(suggestedFileName);
StreamResult result = new StreamResult(file);
System.out.println(file.toURI().toURL().toString());
result.setSystemId(file.toURI().toURL().toString());
return result;
}
}
}
Finally here is a sample XML which can be used to test the code:
<?xml version="1.0" encoding="US-ASCII" standalone="yes"?>
<ns2:xmlTranslationWrapper xmlns:ns2="my.package.namespace">
<TRANSLATIONS>
<TRANSL CODE="123">
<SEVERITY>Information</SEVERITY>
<TRANSLATION>ABC</TRANSLATION>
</TRANSL>
</TRANSLATIONS>
</ns2:xmlTranslationWrapper>
How can I dynamically generate my XSD schema without creating a file equivalent to using a pre-generated XSD schema?
As promised. The idea is simple:
First generate your schema into a DOM result
The parse it from the generated DOM
And, finally, use for validation
I don't think you can do it much better than that. JAXB's schemagen internal structures do not seem to be compatible with javax.xml.validation.Schema. So creating DOM and then parsing it back is the easiest way.
Code example:
public class DynamicSchemaTest {
#XmlRootElement
public static class A {
#XmlAttribute(required = true)
public String name;
public A() {
}
public A(String name) {
this.name = name;
}
}
#Test(expected = MarshalException.class)
public void generatesAndUsesSchema() throws JAXBException, IOException,
SAXException {
final JAXBContext context = JAXBContext.newInstance(A.class);
final DOMResult result = new DOMResult();
result.setSystemId("schema.xsd");
context.generateSchema(new SchemaOutputResolver() {
#Override
public Result createOutput(String namespaceUri,
String suggestedFileName) {
return result;
}
});
#SuppressWarnings("deprecation")
final SchemaFactory schemaFactory = SchemaFactory
.newInstance(WellKnownNamespace.XML_SCHEMA);
final Schema schema = schemaFactory.newSchema(new DOMSource(result
.getNode()));
final Marshaller marshaller = context.createMarshaller();
marshaller.setSchema(schema);
// Works
marshaller.marshal(new A("works"), System.out);
// Fails
marshaller.marshal(new A(null), System.out);
}
}
Lets suppose that we have XML consnensual with Schema and Java class with some common fields:
<objectFromSchema1>
<element1/>
<commonElement1/>
<commonElement2/>
<element2/>
</objectFromSchema1>
public class X {
private String element1;
private String commonElement1;
private String commonElement2;
private String element2;
}
Is a nice way to unmarschall such kind of XML to Java object ? It means: convert all consensual fields and set null on rest.
The answer is "yes". This is the way JAXB works. Take a look on basic JAXB tutorial, e.g. https://jaxb.java.net/tutorial/
http://docs.oracle.com/javase/tutorial/jaxb/intro/
http://www.vogella.com/tutorials/JAXB/article.html
"YES"
If you have an xsd you also could generate automatically these classes by <artifactId>maven-jaxb2-plugin</artifactId>maven plugin.
An example of your Class
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "objectFromSchema1", propOrder = {
})
#XmlRootElement(name = "objectFromSchema1")
public class ObjectFromSchema1
implements Serializable
{
private final static long serialVersionUID = 12343L;
protected String element1;
protected String element2;
protected String commonElement1;
protected String commonElement2;
public String getElement1() {
return element1;
}
public void setElement1(String element1) {
this.element1 = element1;
}
public String getElement2() {
return element2;
}
public void setElement2(String element2) {
this.element2 = element2;
}
public String getCommonElement1() {
return commonElement1;
}
public void setCommonElement1(String commonElement1) {
this.commonElement1 = commonElement1;
}
public String getCommonElement2() {
return commonElement2;
}
public void setCommonElement2(String commonElement2) {
this.commonElement2 = commonElement2;
}
}
Main method to use it
public static void main(String[] args) throws JAXBException {
final JAXBContext context = JAXBContext.newInstance(ObjectFromSchema1.class);
final Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
final ObjectFromSchema1 objectFromSchema1 = new ObjectFromSchema1();
objectFromSchema1.setCommonElement1("commonElement1");
objectFromSchema1.setCommonElement2("commonElement2");
objectFromSchema1.setElement1("element1");
objectFromSchema1.setElement2("element2");
m.marshal(objectFromSchema1, System.out);
}
output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectFromSchema1>
<element1>element1</element1>
<element2>element2</element2>
<commonElement1>commonElement1</commonElement1>
<commonElement2>commonElement2</commonElement2>
</objectFromSchema1>
When marshaling an object via JAXB with a StringBuffer attribute, that attribute becomes blank. I wrote a small program to demonstrate the problem:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class JaxbTest {
private String valueOne;
private StringBuffer valueTwo;
public static void main(String[] args) throws Exception {
JaxbTest object = new JaxbTest();
object.setValueOne("12345");
object.setValueTwo(new StringBuffer("54321"));
JAXBContext context = JAXBContext.newInstance(JaxbTest.class);
Marshaller marshaller = context.createMarshaller();
marshaller.marshal(object, System.out);
}
#XmlElement
public String getValueOne() {
return valueOne;
}
public void setValueOne(String valueOne) {
this.valueOne = valueOne;
}
#XmlElement
public StringBuffer getValueTwo() {
return valueTwo;
}
public void setValueTwo(StringBuffer valueTwo) {
this.valueTwo = valueTwo;
}
}
The output is as follows:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><jaxbTest><valueOne>12345</valueOne><valueTwo/></jaxbTest>
Does anyone know why "valueTwo" is not being marshaled correctly? BTW, i am using java 1.6.0_22.
Thanks in advance!!!
I would recommend using JAXB's XmlAdapter for this use case:
http://bdoughan.blogspot.com/2010/07/xmladapter-jaxbs-secret-weapon.html
It is likely that JaxB does not know how to serialize a StringBuffer. What I would do to solve this kind of issues, is to have a pair of getters/setters:
the one you currently have
one which returns a String and annotated with #XmlElement
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class JaxbTest {
private String valueOne;
private StringBuffer valueTwo;
public static void main(String[] args) throws Exception {
JaxbTest object = new JaxbTest();
object.setValueOne("12345");
object.setValueTwo(new StringBuffer("54321"));
JAXBContext context = JAXBContext.newInstance(JaxbTest.class);
Marshaller marshaller = context.createMarshaller();
marshaller.marshal(object, System.out);
}
#XmlElement
public String getValueOne() {
return valueOne;
}
public void setValueOne(String valueOne) {
this.valueOne = valueOne;
}
public StringBuffer getValueTwo() {
return valueTwo;
}
public void setValueTwo(StringBuffer valueTwo) {
this.valueTwo = valueTwo;
}
#XmlElement
public String getValueTwoString() {
return valueTwo!=null?valueTwo.toString():null;
}
public void setValueTwoString(String valueTwo) {
this.valueTwo = new StringBuffer(valueTwo);
}
}
I am not completely sure, but I think that if you use #XmlElement(name="valueTwo") on the getValueTwoString() method, you should get exactly what you want.
When I have marshaling issues with simple types, I tend to create an extra getter (and possibly setter) to simplify it. Then I add an #XmlIgnore to the main field and set the name of the new field to that of the old one. Example below:
#XmlRootElement
public class JaxbTest {
private String valueOne;
private StringBuffer valueTwo;
public static void main(String[] args) throws Exception {
JaxbTest object = new JaxbTest();
object.setValueOne("12345");
object.setValueTwo(new StringBuffer("54321"));
JAXBContext context = JAXBContext.newInstance(JaxbTest.class);
Marshaller marshaller = context.createMarshaller();
marshaller.marshal(object, System.out);
}
#XmlElement
public String getValueOne() {
return valueOne;
}
public void setValueOne(String valueOne) {
this.valueOne = valueOne;
}
#XmlIgnore
public StringBuffer getValueTwo() {
return valueTwo;
}
public void setValueTwo(StringBuffer valueTwo) {
this.valueTwo = valueTwo;
}
#XmlElement(name="valueTwo")
public String getValueTwoString() {
return valueTwo.toString();
}
public void setValueTwoString(String valueTwo) {
this.valueTwo = new StringBuffer(valueTwo);
}
}
Thanks for all the prompt and wonderful answers!!
I got this problem when using the servicemix-exec component in ServiceMix 4.2, which is caused by this ExecResponse class. It is using a StringBuffer for the "outputData" and "errorData" attribute.