I try to add an error object to response (with HTTP status 422). It works fine, but I want also to add the scheme of my error object to the automatically generated WADL.
Code:
JAX-B model classes:
#XmlRootElement(namespace = "http://www.test.com/test")
#XmlAccessorType(value = XmlAccessType.FIELD)
public class UnprocessableEntityError {
#XmlElement
private String key;
public String getKey() {
return key;
}
public void setKey(final String key) {
this.key = key;
}
}
#XmlRootElement(namespace = "http://www.test.com/")
public class TestModel {
}
JAX-RS resource class:
#Path("test")
public class TestResource {
#POST
public TestModel doSomething() {
throw new WebApplicationException("Error", Response.status(422).entity(new UnprocessableEntityError()).build());
}
}
CXF configuration:
<jaxrs:server address="/rest" id="test" staticSubresourceResolution="true">
<jaxrs:serviceBeans>
<ref bean="testResource" />
</jaxrs:serviceBeans>
<jaxrs:providers>
<bean class="org.apache.cxf.jaxrs.provider.JAXBElementProvider" />
<bean class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider" />
</jaxrs:providers>
</jaxrs:server>
WADL:
<?xml version="1.0"?>
<application xmlns:prefix1="http://www.test.com/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://wadl.dev.java.net/2009/02">
<grammars>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.test.com/" targetNamespace="http://www.test.com/" elementFormDefault="unqualified" attributeFormDefault="unqualified">
<xs:complexType name="testModel">
<xs:sequence/>
</xs:complexType>
</xs:schema>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.test.com/" targetNamespace="http://www.test.com/" elementFormDefault="unqualified" attributeFormDefault="unqualified">
<xs:import/>
<xs:element name="testModel" type="testModel"/>
</xs:schema>
</grammars>
<resources base="http://localhost:8080/test-app/services/rest/1">
<resource path="/test">
<method name="POST">
<response>
<representation mediaType="*/*" element="prefix1:testModel"/>
</response>
</method>
</resource>
</resources>
</application>
Is there any way, to add (just for documentation) an additional element to the grammar of an automatically generated WADL?
You can customize WADL automatic generation but is not very flexible. The general CXF documentation is here but is not helpful.
In the area of <grammars> you can include a custom XSD or link one
Define a WadlGenerator in CXF spring file, and include in the jaxrs provider. For example with books.xsd
<jaxrs:server address="/rest" id="test" >
<jaxrs:providers>
<ref bean="wadlGenerator" />
</jaxrs:providers>
</jaxrs:server>
<bean id="wadlGenerator" class="org.apache.cxf.jaxrs.model.wadl.WadlGenerator">
<property name="schemaLocations" value="classpath:/books.xsd"/>
</bean>
Programmatically is also possible
WadlGenerator wg = new WadlGenerator();
wg.setSchemaLocations(Collections.singletonList("classpath:/books.xsd"));
The generated WADL will be like this
<application xmlns="http://wadl.dev.java.net/2009/02" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<grammars>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://superbooks" attributeFormDefault="unqualified" elementFormDefault="unqualified" targetNamespace="http://superbooks">
<xs:element name="thebook" type="tns:book"/>
<xs:complexType name="book">
<xs:sequence>
<xs:element minOccurs="0" name="chapter" type="xs:string"/>
<xs:element name="id" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
</grammars>
Also, you can include a link
<bean id="wadlGenerator" class="org.apache.cxf.jaxrs.model.wadl.WadlGenerator">
<property name="externalLinks" value="http://books"/>
</bean>
WADL
<grammars>
<include href="http://books"/>
</grammars>
The full documentation of WadlGenerator. I have tested with CXF 2.7
EDITED
Custom WADL Generator
CXF WadlGenerator fills the 'grammars' section with XSD, external links or the autogenerated wadl, but do not allow to combine them. To add an external resource link to generated grammar is needed to create a custom WadlGenerator
<grammars>
<include href="http://books"/>
<!-- The autogenerated grammar-->
</grammars>
This is a fully functional class for CXF 2.7, but I hope it works at any later version because uses inheritance and rewrite only a minimal part of code. It concats externalLinks (if exists) with the autogenerated code or XSD
package com.wadl;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import javax.ws.rs.core.UriInfo;
import javax.xml.bind.JAXBContext;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.jaxrs.model.ResourceTypes;
import org.apache.cxf.jaxrs.model.wadl.WadlGenerator;
public class CustomWadlGenerator extends WadlGenerator {
private List<URI> externalSchemaLinks;
private static final Logger LOG = LogUtils.getL7dLogger(CustomWadlGenerator.class);
//Overwrite setExternalLink so that it is not used in the superclass
#Override
public void setExternalLinks(List<String> externalLinks) {
externalSchemaLinks = new LinkedList<URI>();
for (String s : externalLinks) {
try {
String href = s;
if (href.startsWith("classpath:")) {
int index = href.lastIndexOf('/');
href = index == -1 ? href.substring(9) : href.substring(index + 1);
}
externalSchemaLinks.add(URI.create(href));
} catch (Exception ex) {
LOG.warning("Not a valid URI : " + s);
externalSchemaLinks = null;
break;
}
}
}
private class ExternalSchemaWriter implements WadlGenerator.SchemaWriter {
private List<URI>links;
private UriInfo uriInfo;
private SchemaWriter writer;
public ExternalSchemaWriter(List<URI>links, UriInfo ui, SchemaWriter writer){
this.links = links;
this.uriInfo = ui;
this.writer = writer;
}
public void write(StringBuilder sb) {
//write links
for (URI link : links) {
try {
URI value = link.isAbsolute() ? link : uriInfo.getBaseUriBuilder().path(link.toString()).build(new Object[0]);
sb.append("<include href=\"").append(value.toString()).append("\"/>");
} catch (Exception ex) {
CustomWadlGenerator.LOG.warning("WADL grammar section will be incomplete, this link is not a valid URI : " + link.toString());
}
}
//concat with default writer
writer.write(sb);
}
}
#Override
protected SchemaWriter createSchemaWriter(ResourceTypes resourceTypes, JAXBContext context, UriInfo ui) {
SchemaWriter schemaCollectionWriter = super.createSchemaWriter(resourceTypes, context, ui);
if (externalSchemaLinks == null){
//default behaviour
return schemaCollectionWriter;
} else {
//use custom writer
return new ExternalSchemaWriter(externalSchemaLinks,ui,schemaCollectionWriter);
}
}
}
Set WadlGenerator in spring config
<bean id="wadlGenerator" class="com.wadl.CustomWadlGenerator">
<property name="externalLinks" value="http://books"/>
</bean>
Related
I want to convert this XMl into java Object but nit getting how to make POJO class for it. I can not change this xml because it is comning from remote server as a request and I need all information that it contains in order to response.
<?xml version="1.0" encoding="utf-8"?>
<methodCall>
<methodName>name</methodName>
<params>
<param><value><struct>
<member>
<name>subscriberInput</name>
<value><string>678</string></value>
</member>
<member>
<name>language</name>
<value><string>en</string></value>
</member>
<member>
<name>sessionId</name>
<value><string>16414746570268014</string></value>
</member>
<member><name>msisdn</name><value><string>1234</string></value>
</member>
<member>
<name>newRequest</name>
<value><string>1</string></value>
</member>
<member>
<name>transactionId</name>
<value><string>0122716414746578950</string>
</value>
</member>
</struct>
</value>
</param>
</params>\</methodCall>
You could use the combination of the following tools:
Parse XML to JSON (https://www.convertjson.com/xml-to-json.htm)
Create POJOs from JSON (https://json2csharp.com/json-to-pojo)
The result is the next:
// import com.fasterxml.jackson.databind.ObjectMapper; // version 2.11.1
// import com.fasterxml.jackson.annotation.JsonProperty; // version 2.11.1
/* ObjectMapper om = new ObjectMapper();
Root root = om.readValue(myJsonString), Root.class); */
public class Value2{
public String string;
public Struct struct;
}
public class Member{
public String name;
public Value value;
}
public class Struct{
public List<Member> member;
}
public class Param{
public Value value;
}
public class Params{
public Param param;
}
public class MethodCall{
public String methodName;
public Params params;
}
public class Root{
public MethodCall methodCall;
}
I hope I was able to help you.
Pojo Approach
Taking this xml to https://codebeautify.org/xml-to-java-converter , it produces us ("quickly", "good") results:
We can copy&paste the generated class, but still need to refactor little:
un-public all classes (or move them to separate files)
complete getters/setters of Struct
But, we "suspect already": The problem is in the details and details are in the structure:
package com.example.simpleschema;
import java.util.ArrayList;
public class MethodCall {
private String methodName;
Params ParamsObject;
// Getter Methods
public String getMethodName() {
return methodName;
}
public Params getParams() {
return ParamsObject;
}
// Setter Methods
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public void setParams(Params paramsObject) {
this.ParamsObject = paramsObject;
}
}
class Params {
Param ParamObject;
// Getter Methods
public Param getParam() {
return ParamObject;
}
// Setter Methods
public void setParam(Param paramObject) {
this.ParamObject = paramObject;
}
}
class Param {
Value ValueObject;
// Getter Methods
public Value getValue() {
return ValueObject;
}
// Setter Methods
public void setValue(Value valueObject) {
this.ValueObject = valueObject;
}
}
class Value {
Struct StructObject;
// Getter Methods
public Struct getStruct() {
return StructObject;
}
// Setter Methods
public void setStruct(Struct structObject) {
this.StructObject = structObject;
}
}
class Struct {
ArrayList< Object> member = new ArrayList< Object>();
public ArrayList<Object> getMember() {
return member;
}
public void setMember(ArrayList<Object> member) {
this.member = member;
}
}
With (only) this in our pom:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.13.1</version> <!-- or newer -->
</dependency>
We can do that:
package com.example.xmltest.simple;
import com.example.simpleschema.MethodCall;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.io.IOException;
public class MainSimple {
public static void main(String[] args) throws IOException {
final XmlMapper xmlMapper = new XmlMapper();
MethodCall value = xmlMapper
.readValue(MainSimple.class.getResourceAsStream("/test.xml"), MethodCall.class);
System.out.println(value);
xmlMapper.writeValue(System.out, value);
}
}
Prints:
com.example.simpleschema.MethodCall#23529fee
<MethodCall><methodName>name</methodName><params><param><value><struct><member><member>transactionId</member><member><string>0122716414746578950</string></member></member></struct></value></param></params></MethodCall>
With JAXB
We need to an xsd first! We can:
ask our provider, if he would supply us... or
we generate it ourself...
A "quick" lookup leads us (nowadays, with open-source) to https://learn.microsoft.com/en-us/visualstudio/xml-tools/how-to-create-an-xml-schema-from-an-xml-document .
click-clack in vscode, and our xsd looks like:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="methodCall">
<xs:complexType>
<xs:sequence>
<xs:element name="methodName" type="xs:string" />
<xs:element name="params">
<xs:complexType>
<xs:sequence>
<xs:element name="param">
<xs:complexType>
<xs:sequence>
<xs:element name="value">
<xs:complexType>
<xs:sequence>
<xs:element name="struct">
<xs:complexType>
<xs:sequence>
<xs:element name="member" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string" />
<xs:element name="value">
<xs:complexType>
<xs:sequence>
<xs:element name="string" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Putting this test.xsd under src/main/xsd (standard loaction of codehaus:jaxb2-maven-plugin), and add the following to our pom (sorry #all gradel-users):
...
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>2.5.0</version> <!-- this depends on org.glassfish.jaxb:2.3.2!! -->
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- The (target) package of your generated sources -->
<packageName>com.example.jaxbschema</packageName> <!-- .${project.version} -->
</configuration>
</plugin>
</plugins>
</build>
...
On the next mvn process-classes (or above/or accomplished by IDE), this will generate us some "fine" classes in src/target/generated-sources/jaxb/...
These are not compilable!! (Or the structure is too recursive, or it is a jaxb(impl)-bug, consider it as you like)
We can fix!
Manually ... but have to move the generated com.example.myschema to "main" sources, de-activate the jaxb-plugin, and fix the name clash caused by the most inner Value:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "value", propOrder = {
"string"
})
public static class Value2 { ...
... and resolving remaining compiler conflicts.
This (generated&adjusted model) seems still to be buggy at least for jackson.
With this, spring-boot and spring-oxm:
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring OXM: needs jaxb -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<!-- managed by spring-boot: 5.3.14 -->
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<!-- managed by spring-boo: 2.3.1 -->
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<scope>runtime</scope>
<!-- managed by spring-boo: 2.3.5 -->
</dependency>
</dependencies>
...
We can:
// ...
import com.example.jaxbschema.MethodCall; // !! same as generated/refactored.
// ...
#SpringBootApplication
public class SpringMain {
public static void main(String[] args) {
SpringApplication.run(SpringMain.class, args);
}
#Bean
org.springframework.oxm.jaxb.Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller mler = new org.springframework.oxm.jaxb.Jaxb2Marshaller();
mler.setClassesToBeBound(MethodCall.class);
return mler;
}
#Bean
#Autowired
CommandLineRunner runner(
#Value("test.xml") Resource xmlFile,
org.springframework.oxm.jaxb.Jaxb2Marshaller jaxb2Marshaller
) {
return (args) -> {
MethodCall readValue2 = (MethodCall) jaxb2Marshaller.unmarshal(new StreamSource(xmlFile.getInputStream()));
jaxb2Marshaller.marshal(readValue2, new StreamResult(System.out));
};
}
}
...which prints:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><methodCall><methodName>name</methodName><params><param><value><struct><member><name>subscriberInput</name><value><string>678</string></value></member><member><name>language</name><value><string>en</string></value></member><member><name>sessionId</name><value><string>16414746570268014</string></value></member><member><name>msisdn</name><value><string>1234</string></value></member><member><name>newRequest</name><value><string>1</string></value></member><member><name>transactionId</name><value><string>0122716414746578950</string></value></member></struct></value></param></params></methodCall>
When Structure/XML is big/complex
..and we only need "few values" form that, we should consider a more effcient (parser based) approach...
I have searched for this in Internet but unable to find a answer.
I am new to SOAP services, If I am wrong at any point please correct me. I was trying to generate Java classes from wsdl using command line tool ** JAX-WS RI wsimport**. I was able to generate all Request and Response classes.
The ObjectFactory.java generated from wsdl was missing most of the methods.
employee.wsdl.jaxb.bnd.xml
<jaxb:bindings version="2.0"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb/xjc">
<jaxb:bindings>
<jaxb:globalBindings generateElementProperty="false">
<xjc:serializable>
</jaxb:globalBindings>
</jaxb:bindings>
</jaxb:bindings>
employee.wsdl.bnd.xml
<jaxws:bindings xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns="http://java.sun.com/xml/ns/jaxws"
xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
wsdlLocation="../sp.wsdl">
<jaxws:enableWrapperStyle>false</jaxws:enableWrapperStyle>
</jaxws:bindings>
In jaxb.bnd.xml
While trying with <jaxb:globalBindings generateElementProperty="true"> Its able to generate all of the factory methods. But it was using JAXBElement<> wrapper for primititve data types like String,int,Long
While trying with <jaxb:globalBindings generateElementProperty="fasle"> Its able to generate the class without JAXBElement<> wrapper, but missing most of the factory methods.
cmd: wsimport META-INF/wsdl/employee.wsdl -keep -b META-INF/wsdl/bindings/employee.bnd.xml -b META-INF/wsdl/bindings/employee.jaxb.bnd.xml -wsdllocation META-INF/wsdl/employee.wsdl
Sample ObjectFactory.java
package xyz;
#XmlRegistry
class ObjectFactory {
private final static Qname _EmpDetailsSpecialEmpDetail_QNAME = new QName("", "scheduleEmp");
#XmlElementDecl(namespce = "", name="specialEmployee", scope= EmpDetails.class)
public SpecialEmpDetail EmpDetailsSpecialEmployee(SpecialEmpDetail value) {
return new SpecialEmpDetail(_EmpDetailsSpecialEmpDetail_QNAME, SpecialEmpDetail.class, EmpDetail.class, value);
}
EmpDetail.java
public class EmpDetail implements Serializable {
#XmlElementRef(name= "specialEmployee", required=false)
protected SpecialEmpDetail specialEmployee;
public SpcialEmpDetail getSpecialEmployee() {
return specialEmployee;
}
public SpcialEmpDetail setSpecialEmployee(SpcialEmpDetail value) {
this.specialEmployee = value;
}
}
SpecialEmpDetail.java
public class SpecialEmpDetail {
//local var
//getter and setter
.....
}
employee.wsdl
<?xml version="1.0" encoding="UTF-8">
<definitions name="psp"
targetNamespace="http://www.namespace.com/employee.wsdl"
.....
/>
<types>
<schmea targerNamespace = "http://www.namespace.com/employee.wsdl"
xmlns:SOAP-ENV = "http://www.w3.org/2003/05/soap-envelope"
xmlns:xsi = "http://www.w3.org/2003/05/XMLSchema-instance"
xmlns:xsd = "http://www.w3.org/2001/XMLSchema"
xmlns:emp = "http://www.namespace.com/employee/employee.xsd1"
<import namespace="http://www.namespace.com/employee.xsd1"/>
<import namespace="http://www.w3.org/2003/05/soap-encoding"/>
</schmea>
<schmea targerNamespace = "http://www.namespace.com/employee.xsd1"
xmlns:SOAP-ENV = "http://www.w3.org/2003/05/soap-envelope"
xmlns:xsi = "http://www.w3.org/2003/05/XMLSchema-instance"
xmlns:xsd = "http://www.w3.org/2001/XMLSchema"
xmlns:emp = "http://www.namespace.com/employee/employee.xsd1"
<import namespace="http://www.namespace.com/employee.wsdl"/>
<import namespace="http://www.w3.org/2003/05/soap-encoding"/>
<simpleType name="empName">
<restrictions base="xsd:string">
<minLength value="1"/>
<maxLength value="30"/>
</restrictions>
<simpleType name="EmpID">
<restrictions base="xsd:string">
<minLength value="0"/>
<maxLength value="25"/>
</restrictions>
</simpleType>
<complexType name="">
<sequence>
<element name="specialEmployee" type="emp:SpecialEmpDetail minOccurs="0" maxOccurs="1" />
</sequence>
</complexType>
.....
.........
</schema>
</types>
Here is the sample wsdl and java. I can able to generate SpecialEmpDetail.java & EmpDetail.java The ObjectFctory.java should create the mentioned function, but its failed to do so.... Using JAXBElement<> wrapper I can able to generate all functions but the problem here is its a complextType data so wrapping is fine, But String also wrapped this is not expected.
What may be problem?? How can I resolve it??
Thanks in Advance!
in my schema I have following element:
<xs:element name="deletePokemonsRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="pokemonId" type="xs:int" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
And I have endpoint for it:
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "deletePokemonsRequest")
#ResponsePayload
public DeletePokemonsRequest deletePokemons(#RequestPayload DeletePokemonsRequest deletePokemons){
pokemonDAO.deletePokemons(deletePokemons.getPokemonId());
return deletePokemons;
}
When I send on this endpoint:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:pok="www">
<soapenv:Header/>
<soapenv:Body>
<pok:deletePokemonsRequest>
</pok:deletePokemonsRequest>
</soapenv:Body>
</soapenv:Envelope>
It is accepted, but it should be rejected on validation stage. Why ? Because I set minOccurs=1, but it accepted envelope with 0 elements.
How to turn on validation according to WSDL ?
Configure a validating interceptor.
xml config
<bean id="validatingInterceptor" class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
<property name="xsdSchema" ref="schema" />
<property name="validateRequest" value="true" />
<property name="validateResponse" value="true" />
</bean>
<bean id="schema" class="org.springframework.xml.xsd.SimpleXsdSchema">
<property name="xsd" value="your.xsd" />
</bean>
or with java config
#Configuration
#EnableWs
public class MyWsConfig extends WsConfigurerAdapter {
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
PayloadValidatingInterceptor validatingInterceptor = new PayloadValidatingInterceptor();
validatingInterceptor.setValidateRequest(true);
validatingInterceptor.setValidateResponse(true);
validatingInterceptor.setXsdSchema(yourSchema());
interceptors.add(validatingInterceptor);
}
#Bean
public XsdSchema yourSchema(){
return new SimpleXsdSchema(new ClassPathResource("your.xsd"));
}
// snip other stuff
}
I've got an xml file that looks like this
<console-menu-entry index="2" text="Print Hello World">
<console-menu-entry index="1" text="Print Hello">
print 'Hello'
</console-menu-entry>
<console-menu-entry index="2" text="Print World">
print 'World'
</console-menu-entry>
</console-menu-entry>
Basically node <console-menu-entry> may have either tags inside it or some text value.
How to process it using jaxb? When I do like this it fails with
If a class has #XmlElement property, it cannot have #XmlValue property.
error.
My class looks like this
#XmlRootElement(name="console-menu-entry")
#XmlAccessorType(XmlAccessType.FIELD)
#ToString
public #Data class XmlConsoleMenuEntry {
#XmlAttribute
private String index;
#XmlAttribute
private String text;
#XmlValue
private String value;
#XmlElement(name="console-menu-entry")
private List<XmlConsoleMenuEntry> entries;
}
P.S. Using jaxb is not a requirement so if there is an approach using another library I am open to suggestions.
I would suggest to use JAXB but come from another end. Try to make use of xsd schema. This approach has a number of advantages:
Developing an XSD lets you understand your datamodel more deeply, detect possible flaws and see the data structure more clear.
You can use XSD to generate the parser which will help you to parse your xml (meeting that model) in a few lines of code
You can use XSD to create xml files (all the modern IDEs integrate XSD data in code-assistance mechanisms) so that you will get advice from IDE on which element would fit your datamodel in a particular place, which attribute is requited for a particular element (even which values are suitable for that attrubute) and many other usefule things
Below is the example of xsd schema which will let you generate parser which parses your example (you should only provide appropriate namespace for your xml elements)
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://some.your.schema"
xmlns:tns="http://some.your.schema"
elementFormDefault="qualified">
<xs:complexType name="ConsoleMenuEntry" mixed="true" >
<xs:sequence>
<xs:element ref="tns:console-menu-entry" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="index" type="xs:integer" use="required"/>
<xs:attribute name="text" type="xs:string" use="required"/>
</xs:complexType>
<xs:element name="console-menu-entry" type="tns:ConsoleMenuEntry"/>
</xs:schema>
You now can generate the parser files (Windows example)
"%JAVA_HOME%\bin\xjc" -d ../src -p your.app.generated test.xsd
Where -d ../src specifies the path on hard drive where your parser classes would be located, -p your.app.generated specifies the package for you generated parser classes and test.xsd is the schema file name
Here is the example of test.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<console-menu-entry xmlns="http://some.your.schema" index="1" text="Some new text">
<console-menu-entry index="1" text="some other text">
sdfsdkljf
</console-menu-entry>
<console-menu-entry index="2" text="some other text"/>
</console-menu-entry>
And the code that parses it:
public class Main {
public static void main(String[] args) throws JAXBException {
JAXBContext jc = JAXBContext.newInstance(ObjectFactory.class);
ConsoleMenuEntry rootEntry = ((JAXBElement<ConsoleMenuEntry>) jc.createUnmarshaller().unmarshal(new File("PATH_TO_FILE\\test.xml"))).getValue();
processMenuEntry(rootEntry);
}
private static void processMenuEntry(ConsoleMenuEntry menuEntry) {
System.out.println("Index (attr) = " + menuEntry.getIndex() + ", Text (attr) = '" + menuEntry.getText() + "'");
for (Serializable element : menuEntry.getContent()) {
if (element instanceof JAXBElement) {
processMenuEntry(((JAXBElement<ConsoleMenuEntry>) element).getValue());
} else if (element instanceof String) {
String innerText = element.toString().trim();
if (innerText.length() > 0) {
System.out.println("Inner text: '" + innerText);
}
}
}
}
}
I am having trouble customizing my JAXB Marshaller. I have my marshaller code:
public void marshaller(AddressMap addMap, File file) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(AddressMap.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(addMap, System.out);
} catch (JAXBException e) {
e.printStackTrace();
}
}
The output looks like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ObjectMap>
<Prop> Indiana</Prop>
<Prop1>39.0</Prop1>
<Prop2>-85.0</Prop2>
<Prop3> United States</Prop3>
<Prop4> Hueseman Rd</Prop4>
<Prop5> 8540-8704</Prop5>
<Prop6> 47001</Prop6>
</ObjectMap>
Instead, I need it to look like this:
<bean class="classname">
<property name="PropName" value="object value" />
<property name="PropName1" value="object value" />
<property name="PropName2" value="object value" />
<property name="PropName3" value="object value" />
<property name="PropName4" value="object value" />
<property name="PropName5" value="object value" />
<property name="PropName6" value="object value" />
</bean>
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
You could use MOXy's #XmlDesciminatorNode and #XmlPath extensions to map this use case. Below is an example based on what I assume your object model looks like.
ObjectMap
The #XmlDescriminatorNode annotation allows you to specify that you want a specific XML attribute to serve as the inheritance indicator.
package forum13884782;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
#XmlRootElement(name="bean")
#XmlDiscriminatorNode("#class")
public class ObjectMap {
}
AddressMap
The #XmlDescriminatorValue annotation is used to specify the value on the descriminator node that relates to the instance class. In this class we also use the #XmlPath annotation to indicate which property element we wish to map to based on the value of its name attribute.
package forum13884782;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
#XmlRootElement(name="bean")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlDiscriminatorValue("AddressMap")
public class AddressMap extends ObjectMap {
#XmlPath("property[#name='PropName']/#value")
String prop;
#XmlPath("property[#name='PropName1']/#value")
String prop1;
#XmlPath("property[#name='PropName2']/#value")
String prop2;
#XmlPath("property[#name='PropName3']/#value")
String prop3;
#XmlPath("property[#name='PropName4']/#value")
String prop4;
#XmlPath("property[#name='PropName5']/#value")
String prop5;
#XmlPath("property[#name='PropName6']/#value")
String prop6;
}
jaxb.properties
To specify MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry.
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
The following demo code will convert the XML message to an instance of AddressMap and then back to XML.
package forum13884782;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(AddressMap.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum13884782/input.xml");
AddressMap addressMap = (AddressMap) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(addressMap, System.out);
}
}
input.xml/Output
<bean class="AddressMap">
<property name="PropName" value="Indiana" />
<property name="PropName1" value="39.0" />
<property name="PropName2" value="-85.0" />
<property name="PropName3" value="United States" />
<property name="PropName4" value="Hueseman Rd" />
<property name="PropName5" value="8540-8704" />
<property name="PropName6" value="47001" />
</bean>
For More Information
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-moxy-extension.html
http://blog.bdoughan.com/2011/03/map-to-element-based-on-attribute-value.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html