I have a XML file which has that kind of structure:
<a:root>
<a:body>
<b:do_action>
<b:do_input>
<request>
<!-- There are a lot of primitive elements -->
</request>
</b:do_input>
</b:do_action>
</a:body>
</a:root>
I'm trying to parse this XML by using SimpleXML:
public class Request {
// There are a lot of defined primitive elements
}
#Root(name = "root")
#Namespace(prefix = "a")
public class Root {
#Path("a:body/b:do_action/b:do_input")
#Element(name = "request")
public Request request;
}
When I instantiate my object and want to show it as string, I get this error message:
org.simpleframework.xml.core.ElementException: Namespace prefix 'b' in class Request is not in scope
How to deal with paths, which have different prefixes?
Since you use two different namespaces, you should declare both of them:
#Root(name = "root")
#NamespaceList({
#Namespace(prefix = "a" , reference="ref_a"),
#Namespace(prefix = "b", reference="ref_b")})
public class Root {
}
Hope it helps.
Related
I'm working with the ECCP protocol in order to integrate my CRM with the Elastix Call Center Module. The protocol uses a XML structure defined as follows:
<request id="1">
<request_type> <!-- this will be mapped to the Java request class -->
<attributes>
</attributes>
</request_type>
</request>
and
<response id="1">
<response_type> <!-- this will be mapped to the Java response class -->
<attributes>
</attributes>
</response_type>
</response>
I'm using JAX-B to map XML to Java classes but the problem is that I have to put the JAX-B generated XML inside a <request></request> XML every request and extract the content from <response></response> in every response because the ECCP protocol defines that every request and response needs to nested to their respective elements.
Here's the code I'm using to do that:
document = createDocument();
Element requestWrapper = document.createElement("request");
requestWrapper.setAttribute("id", String.valueOf(wrapped.getId()));
document.appendChild(requestWrapper);
JAXBContext jc = JAXBContext.newInstance(wrapped.getClass());
Marshaller marshaller = jc.createMarshaller();
marshaller.marshal(wrapped, requestWrapper);
Exemplifying:
One of ECCP's protocol operation is JAX-B-mapped into a class like this (getters and setters were omitted):
#XmlRootElement(name = "loginagent")
#XmlAccessorType(XmlAccessType.FIELD)
public class EccpLoginAgentRequest implements IEccpRequest {
#XmlElement(name = "agent_number")
private String agentNumber;
#XmlElement(name = "password")
private String password;
}
And JAX-B outputs the following:
<loginagent>
<agent_number>username</agent_number>
<password>password</password>
</loginagent>
But what the ECCP's protocol requires is:
<request id="1"> <!-- id is an auto-increment number to identify the request -->
<loginagent>
<username>username</username>
<password>password</password>
</loginagent>
</request>
The question is: is there any other way to achieve in any other better way?
Thank you.
You can probably check out the #XmlSeeAlso annotation which will help you wrap the same content both for request and response. For the inner part you can create the separate class and map all the fields appropriately. I hope this helps you a little bit.
EDIT:
Sorry for the long response time. You need to create a wrapper class with the inner structure defined as an #XmlElement. Here's the way to achieve that XML structure:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "request")
public class RequestWrapper {
#XmlElement(name = "loginagent", required = true)
protected EccpLoginAgentRequest loginagent;
public EccpLoginAgentRequest getLoginagent() {
return loginagent;
}
public void setLoginagent(EccpLoginAgentRequest loginagent) {
this.loginagent = loginagent;
}
}
And here's the EccpLoginAgentRequest structure:
#XmlAccessorType(XmlAccessType.FIELD)
public class EccpLoginAgentRequest {
#XmlElement(name = "agent_number")
private String agentNumber;
#XmlElement(name = "password")
private String password;
// getters and setters omitted
}
So in result, you can print the XML you want like that:
JAXBContext jaxbContext = JAXBContext.newInstance(Wrapper.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
EccpLoginAgentRequest request = new EccpLoginAgentRequest();
request.setAgentNumber("1");
request.setPassword("pass");
Wrapper wrapper = new Wrapper();
wrapper.setLoginagent(request);
jaxbMarshaller.marshal(wrapper, System.out);
It will give you the following output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<request>
<loginagent>
<agent_number>1</agent_number>
<password>pass</password>
</loginagent>
</request>
I found a way to solve this in this post: XML element with attribute and content using JAXB
So I've mapped a EccpRequestWrapper object as the following:
#XmlRootElement(name = "request")
public class EccpRequestWrapper {
#XmlAttribute
private Long id;
#XmlAnyElement
private IEccpRequest request;
}
and then my request JAX-B outputs my request the way ECCP protocol requires.
The #XmlAttribute and #XmlAnyElement annotation did the trick.
<request id="1">
<login>
<username>user</username>
<password>****</password>
</login>
</request>
A good JAXB guide can be found here https://jaxb.java.net/guide/Mapping_interfaces.html
If I have the class A.java:
#JacksonXmlRootElement(localName = "A")
public class A {
}
The output that gets produced is:
<A
xmlns="">
I want to add a few more namespaces to the output, i.e.
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.example.com example.xsd"
How do I configure A.java to contain more custom namespaces like these?
Since xsi:schemaLocation is an attribute, you can add it like that:
public class A implements Serializable {
#JacksonXmlProperty(isAttribute = true, localName = "xsi:schemaLocation")
private String schemaLocation = "urn:path:to.your.schema";
It did the job for me.
I'm new to using namespaces in xml so I am kind of confused and would like some clarification. I have a java service where I am receiving xml documents with many different namespaces and while i got it working, I feel like I must have done something wrong so I want to check. In my package-info.java I have my schema annotation such as:
#javax.xml.bind.annotation.XmlSchema(
xmlns={
#javax.xml.bind.annotation.XmHS(prefix="train", namespaceURI="http://mycompany/train"),
#javax.xml.bind.annotation.XmHS(prefix="passenger", namespaceURI="http://mycompany/passenger")
},
elementFormDefault = javax.xml.bind.annotation.XmlNsForm=QUALIFIED
)
I have a Train.java annotated on the class level with:
#XmlRootElement(name="Train", namespace="http://mycompany/train")
and each field in the class annotated with:
#XmlElement(name="Color") for example
Train contains a List of Passenger(s) so there's a property
private Set<Passenger> passengers;
and this collection is annotated with:
#XmlElementWrapper(name="Passengers")
#XmlElements(#XmlElement(name="Passenger", namespace="http://mycompany/passenger"))
Then within Passenger.java the class itself is annotated with:
#XmlElement(name="Passenger", namespace="http://mycompany/passenger")
Finally for individual fields within Passenger.java, they are annotated like this:
#XmlElement(name="TicketNumber", namespace="http://mycompany/passenger")
So when I have an xml that looks like:
<train:Train>
<train:Color>Red</train:Color>
<train:Passengers>
<train:Passenger>
<passenger:TicketNumber>T101</passenger:TicketNumber>
</train:Passenger>
</train:Passengers>
</train:Train>
Now I unmarshal this xml I received and Train's Color property is set and Passenger's TicketNumber property is set. But I don't know why I need to add the namespace url on the XmlElement annotation on TicketNumber for that to work but I didn't need to do so for the Color property on Train. If I remove the namespace attribute from the XmlElement annotation on TicketNumber, the value from the xml wont get mapped to the object unless I also remove the namespace prefix from the xml request. I feel like since I've got the namespace attribute defined on the XmlRootElement for Passenger, I shouldn't need to do that for every single field in the class as well just like I didn't have to for Train so I am assuming I must have setup something wrong. Can someone point me in the right direction? Thanks!
Below is an explanation of how namespaces work in JAXB (JSR-222) based on your model.
JAVA MODEL
package-info
Below is a modified version of your #XmlSchema annotation. It contains some key information:
namespace - The default namespace that will be used to qualify global elements (those corresponding to #XmlRootElement and #XmlElementDecl annotations (and local elements based on the elementFormDefault value) that don't have another namespace specified.
elementFormDefault by default only global elements are namespace qualified but by setting the value to be XmlNsForm.QUALIFIED all elements without an explicit namespace specified will be qualified with the namespace value.
xmlns is the preferred set of prefixes that a JAXB impl should use for those namespaces (although they may use other prefixes).
#XmlSchema(
namespace="http://mycompany/train",
elementFormDefault = XmlNsForm.QUALIFIED,
xmlns={
#XmlNs(prefix="train", namespaceURI="http://mycompany/train"),
#XmlNs(prefix="passenger", namespaceURI="http://mycompany/passenger")
}
)
package forum15772478;
import javax.xml.bind.annotation.*;
Train
Since all the elements corresponding to the Train class correspond to the namespace specified on the #XmlSchema annotation, we don't need to specify any namespace info.
Global Elements - The #XmlRootElement annotation corresponds to a global element.
Local Elements - The #XmlElementWrapper and #XmlElement annotations correspond to local elements.
package forum15772478;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="Train")
public class Train {
private List<Passenger> passengers;
#XmlElementWrapper(name="Passengers")
#XmlElement(name="Passenger")
public List<Passenger> getPassengers() {
return passengers;
}
public void setPassengers(List<Passenger> passengers) {
this.passengers = passengers;
}
}
Passenger
If all the elements corresponding to properties on the Passenger class will be in the http://mycompany/passenger namespace, then you can use the #XmlType annotation to override the namespace from the #XmlSchema annotation.
package forum15772478;
import javax.xml.bind.annotation.*;
#XmlType(namespace="http://mycompany/passenger")
public class Passenger {
private String ticketNumber;
#XmlElement(name="TicketNumber")
public String getTicketNumber() {
return ticketNumber;
}
public void setTicketNumber(String ticketNumber) {
this.ticketNumber = ticketNumber;
}
}
Alternatively you can override the namespace at the property level.
package forum15772478;
import javax.xml.bind.annotation.*;
public class Passenger {
private String ticketNumber;
#XmlElement(
namespace="http://mycompany/passenger",
name="TicketNumber")
public String getTicketNumber() {
return ticketNumber;
}
public void setTicketNumber(String ticketNumber) {
this.ticketNumber = ticketNumber;
}
}
DEMO CODE
The following demo code can be run to prove that everything works:
Demo
package forum15772478;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Train.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum15772478/input.xml");
Train train = (Train) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(train, System.out);
}
}
input.xml/Output
In the XML below I have added the necessary namespace declarations that were missing from the XML document in your question.
<train:Train
xmlns:train="http://mycompany/train"
xmlns:passenger="http://mycompany/passenger">
<train:Color>Red</train:Color>
<train:Passengers>
<train:Passenger>
<passenger:TicketNumber>T101</passenger:TicketNumber>
</train:Passenger>
</train:Passengers>
</train:Train>
FOR MORE INFORMATION
http://blog.bdoughan.com/2010/08/jaxb-namespaces.html
I'm having troubles mapping a Collection of JAXB object within another JAXB object, anyone see the issue with my structure below? I get an empty formerUsers ArrayList using the following code:
String test="<SSO-Request><User-Id>3119043033121014002</User-Id><Former-User-Ids><User-Id>3119043033121014999</User-Id><User-Id>3119043033121014555</User-Id></Former-User-Ids></SSO-Request>";
SSORequest ssoRequest=null;
try{
JAXBContext jaxbContext = JAXBContext.newInstance(SSORequest.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
ssoRequest = (SSORequest) unmarshaller.unmarshal(new StringReader(test));
}
catch(Exception e){
e.printStackTrace();
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name="SSO-Request")
public class SSORequest {
#XmlElement(name="User-Id")
String userId;
#XmlElementWrapper(name="Former-User-Ids")
List<FormerUser> formerUsers;
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name="Former-User-Ids")
public class FormerUser {
#XmlElement(name="User-Id")
String userId;
}
You're over-complicating your mapping, this is all you need:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name="SSO-Request")
public class SSORequest {
#XmlElement(name="User-Id")
String userId;
#XmlElementWrapper(name="Former-User-Ids")
#XmlElement(name="User-Id")
List<String> formerUserIds;
}
You should either change your mapping as skaffman proposed, or you should change the xml:
<SSO-Request><User-Id>3119043033121014002</User-Id><Former-User-Ids><Former-User><User-Id>3119043033121014999</User-Id></Former-User><Former-User><User-Id>3119043033121014555</User-Id></Former-User></Former-User-Ids></SSO-Request>
and change the name of the the FormerUser xml element:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name="Former-User")
public class FormerUser {
#XmlElement(name="User-Id")
String userId;
}
If the property should be List<FormerUser> then you will need a way to tell JAXB what the ID corresponds to. If the data for FormerUsers will occur in the document then you can use #XmlID and #XmlIDREF for this mapping:
http://bdoughan.blogspot.com/2010/10/jaxb-and-shared-references-xmlid-and.html
If the data for FormerUsers will occur outside the document, then you can use the strategy I described in the answer below:
Using JAXB to cross reference XmlIDs from two XML files
I am using the JAXB that is part of the Jersey JAX-RS. When I request JSON for my output type, all my attribute names start with an asterisk like this,
This object;
package com.ups.crd.data.objects;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
#XmlType
public class ResponseDetails {
#XmlAttribute public String ReturnCode = "";
#XmlAttribute public String StatusMessage = "";
#XmlAttribute public String TransactionDate ="";
}
becomes this,
{"ResponseDetails":{"#transactionDate":"07-12-2010",
"#statusMessage":"Successful","#returnCode":"0"}
So, why are there # in the name?
Any properties mapped with #XmlAttribute will be prefixed with '#' in JSON. If you want to remove it simply annotated your property with #XmlElement.
Presumably this is to avoid potential name conflicts:
#XmlAttribute(name="foo") public String prop1; // maps to #foo in JSON
#XmlElement(name="foo") public String prop2; // maps to foo in JSON
If you are marshalling to both XML and JSON, and you don't need it as an attribute in the XML version then suggestion to use #XmlElement is the best way to go.
However, if it needs to be an attribute (rather than an element) in the XML version, you do have a fairly easy alternative.
You can easily setup a JSONConfiguration that turns off the insertion of the "#".
It would look something like this:
#Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
public JAXBContextResolver() throws Exception {
this.context= new JSONJAXBContext(
JSONConfiguration
.mapped()
.attributeAsElement("StatusMessage",...)
.build(),
ResponseDetails.class);
}
#Override
public JAXBContext getContext(Class<?> objectType) {
return context;
}
}
There are also some other alternatives document here:
http://jersey.java.net/nonav/documentation/latest/json.html
You have to set JSON_ATTRIBUTE_PREFIX in your JAXBContext configuration to "" which by default is "#":
properties.put(JAXBContextProperties.JSON_ATTRIBUTE_PREFIX, "");