I have a data source that has switched from base64 embedded image data to XOP image data. I'm using Java/JAXB to unmarshal the data and can't find any good sources describing how it's done. All references seem to describe doing this with SOAP messages which seem to take care of some of the heavy lifting for you.
In my case, the data is coming in effectively as a string that needs to be unmarshalled into objects created by JAXB. Since the new messages start with
Mime-Version: 1.0
Content-Type: multipart/related; start-info="text/xml"; type="application/xop+xml";
start="<-963165769043289641.1400077877224#xxxxxyyyyyy.ca>";
boundary="----=_Part_0_-338193320.1400077877317"
obviously it won't unmarshal the data as XML as it doesn't look like XML (no data allowed before prolog).
Can this be done with JAXB? The creation of the object from the schema seems to work fine and creates what looks like a proper object (it creates an Include element). I've tried creating an AttachmentUnmarshaller manually but it hasn't helped ... the incoming data is still not recognized as XML. I feel like I'm missing a fundamental step but can't find any good references or examples.
Any help would be greatly appreciated.
Below is an approach that might help.
Message
Below is roughly what you are going to be processing:
MIME-Version: 1.0
Content-Type: Multipart/Related;boundary=MIME_boundary;
...
--MIME_boundary
Content-Type: application/xop+xml;
...
<soap:Envelope ...
<soap:Body>...
<foo>
<photo xmlmime:contentType='image/png'>
<xop:Include xmlns:xop='http://www.w3.org/2004/08/xop/include'
href='cid:http://example.org/me.jpeg'/></m:photo>
...
--MIME_boundary
Content-Type: image/png
Content-Transfer-Encoding: binary
Content-ID: <http://example.org/me.png>
// binary octets for png
Demo Code
Demo
The code below assumes you have processed the message to do the following:
Extract the XML fragment
Extract the attachments and are able to key them on the cid.
import java.io.File;
import javax.xml.bind.*;
public class Demo {
private static String base64 = "/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD5/ooooA//2Q==";
public static void main(String[] args) throws Exception {
// Create the JAXBContext & Unmarshaller
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
// Create a custom AttachementUnmarshaller. Populate the Map
// AttachmentUnmarshaller with the attachments keyed on the cid.
MyAttachmentUnmarshaller attachmentUnmarshaller = new MyAttachmentUnmarshaller();
attachmentUnmarshaller.getAttachments().put("cid:http://example.org/me.jpeg", DatatypeConverter.parseBase64Binary(base64));
// Set the AttachmentUnmarshaller on the Unmarshaller
unmarshaller.setAttachmentUnmarshaller(attachmentUnmarshaller);
// Unmarshal the XML piece
File xml = new File("src/forum24407360/input.xml");
Foo foo = (Foo) unmarshaller.unmarshal(xml);
}
}
MyAttachmentUnmarshaller
Below is what the AttachmentUnmarshaller looks like. An AttachmentUnmarshaller is passed in the cid and is responsible for returning the corresponding binary data. In a JAX-WS environment this is handled automatically for you, but there is nothing preventing you from doing it manually. You can find more information about it here: What's the most standard Java way to store raw binary data along with XML?.
import java.io.*;
import java.util.*;
import javax.activation.*;
import javax.xml.bind.attachment.AttachmentUnmarshaller;
public class MyAttachmentUnmarshaller extends AttachmentUnmarshaller {
private Map<String, byte[]> attachments = new HashMap<String, byte[]>();
public Map<String, byte[]> getAttachments() {
return attachments;
}
#Override
public DataHandler getAttachmentAsDataHandler(String cid) {
byte[] bytes = attachments.get(cid);
return new DataHandler(new ByteArrayDataSource(bytes));
}
#Override
public byte[] getAttachmentAsByteArray(String cid) {
return attachments.get(cid);
}
#Override
public boolean isXOPPackage() {
return true;
}
private static class ByteArrayDataSource implements DataSource {
private byte[] bytes;
public ByteArrayDataSource(byte[] bytes) {
this.bytes = bytes;
}
public String getContentType() {
return "application/octet-stream";
}
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(bytes);
}
public String getName() {
return null;
}
public OutputStream getOutputStream() throws IOException {
return null;
}
}
}
XML Piece
<foo>
<image>
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="cid:http://example.org/me.jpeg"/>
</image>
</foo>
Related
As an upshot (and I can expound by code examples if necessary), I have just realized that my REST API, written in Java, provided by CXF and served by Tomcat 7 is case sensitive when it comes to posting XML content.
Is there any way to make the XML, which usually is a marshalled representation of the entity a service creates, can be case insensitive?
I can certainly post examples of the entity class, service, and their annotations if necessary but as bare minimum, if an instance variable in the entity is private String firstName, the XML tag must be <firstName>...</firstName> and not <firstname>...</firstname> but I would like to make the latter marshall-able.
A complete solution involves a lot of work but it is perfectly possible. Following the link posted by #matiaselgart, the general solution would be
1 - Add a CXF Interceptor to manipulate the Message
2 - Read the incoming content, extract the XML, and process it with a StreamReaderDelegate to convert to lowercase
3 - Replace the content in Message with the output
The JAXB tags should be in lowercase, so the streamer could convert them easily, and be processed by JAXB unmarshaller. In your example private String firstName, the XML tag must be <firstname>...</firstname> and not <firstName>...</firstName>.
CXF Interceptor
public class CaseInsensitiveInterceptor extends AbstractPhaseInterceptor<Message> {
public CaseInsensitiveInterceptor () {
super(Phase.RECEIVE);
}
public void handleMessage(Message message) {
//Get the message body as input stream, process the xml, and set a new non-consumed inputStream into Message
InputStream in = message.getContent(InputStream.class);
InputStream bin = xmlToLowerCase (in);
message.setContent(InputStream.class, bin);
}
public void handleFault(Message messageParam) {
//Invoked when interceptor fails
}
}
Configuration
Add the interceptor in the bus or in the provider
<bean id="caseInsensitiveInterceptor" class="CaseInsensitiveInterceptor " />
<cxf:bus>
<cxf:inInterceptors>
<ref bean="caseInsensitiveInterceptor"/>
</cxf:inInterceptors>
</cxf:bus>
Case Insensitive StreamReaderDelegate
I think you can use the StreamReaderDelegate from here and convert the XMLStreamReader to InputStream using this link . The method xmlToLowerCase is called from interceptor
WARNING: I have not tested this part of the code.
private static class MyStreamReaderDelegate extends StreamReaderDelegate {
public MyStreamReaderDelegate(XMLStreamReader xsr) {
super(xsr);
}
#Override
public String getAttributeLocalName(int index) {
return super.getAttributeLocalName(index).toLowerCase();
}
#Override
public String getLocalName() {
return super.getLocalName().toLowerCase();
}
}
public InputStream xmlToLowerCase (InputStream in){
XMLInputFactory xif = XMLInputFactory.newInstance();
XMLStreamReader xsr = xif.createXMLStreamReader(in);
xsr = new MyStreamReaderDelegate(xsr);
String xml = getOuterXml(xsr);
return new ByteArrayInputStream (xml.getBytes());
}
//https://coderanch.com/t/478588/XMLStreamReader-InputStream
private String getOuterXml(XMLStreamReader xmlr) throws TransformerConfigurationException,
TransformerFactoryConfigurationError, TransformerException
{
Transformer transformer = TransformerFactory.newInstance().newTransformer();
StringWriter stringWriter = new StringWriter();
transformer.transform(new StAXSource(xmlr), new StreamResult(stringWriter));
return stringWriter.toString();
}
I'm currently trying to unmarshal some XML into a java object using JAXB and I'm getting a strange Null Pointer Exception. This is only an issue while unmarshalling. I can marshal with these classes just fine. Here are the relevant pieces of code (irrelevant portions are denoted by "..."):
The JAXB Root element:
...
#XmlRootElement(name="assets")
public class Assets {
String product;
Images images = new Images();
...
Public Assets() {}
...
public String getProduct() { return this.product; }
#XmlAttribute(name="product")
public void setProduct(String newProduct) { this.product = newProduct; }
public Images getImages() { return this.images; }
#XmlElement(name="images")
public void setImages(Images newImages) { this.images = newImages; }
...
}
The Images sub-element of the root element:
...
#XmlRootElement(name="images")
#XmlSeeAlso(Image.class)
#XmlType(propOrder = {"mainAsset", "image"})
public class Images {
List<Image> images;
Image mainAsset;
private static char counter = 'a';
private static final String prefix = "product-image-";
// Here's the part that causes the NPE
#XmlAnyElement(lax=true)
public List<JAXBElement<Image>> getImage() {
final List<JAXBElement<Image>> imageList = new ArrayList<JAXBElement<Image>>();
for (final Image g : this.images) {
imageList.add(new JAXBElement(new QName(prefix + counter++), Image.class, g));
}
counter = 'a';
return imageList;
}
#XmlElement(name="renditions")
public void setImage(List<Image> newImages) { this.images = newImages; }
public Image getMainAsset() { return this.mainAsset; }
#XmlElement(name="main-asset-name")
public void setMainAsset(Image newMainAsset) { this.mainAsset = newMainAsset; }
}
The logic for unmarshalling the XML:
...
public void modifyXML() {
try {
JAXBContext context = JAXBContext.newInstance(Assets.class, Images.class, Image.class);
Unmarshaller um = context.createUnmarshaller();
File f = new File("/path/to/my.xml");
Assets assets = (Assets) um.unmarshal(f);
...
} catch (JAXBException e) {
e.printStackTrace();
}
}
...
Finally, the XML I'm trying to unmarshal (it might help to know that this xml file was actually generated using the JAXB marshaller, which runs without any problems):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assets product="TEST PRODUCT">
<images>
<main-asset-name>
<path>stuff.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</main-asset-name>
<product-image-a>
<path>48x48.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</product-image-a>
<product-image-b>
<path>140x140.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</product-image-b>
<product-image-c>
<path>1280x1280.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</product-image-c>
<product-image-d>
<path>319x319.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</product-image-d>
</images>
</assets>
Okay, so that's all the relevant code (I think). When I run my program, I get the following error right after invoking the unmarshaller:
java.lang.NullPointerException
at com.my.companys.name.Images.getImage(Images.java:25)
And the line number referenced is the line where the for loop starts in my Images.java class file.
Does anyone have any ideas why this.images might be null here? Any help is greatly appreciated. Thanks ahead of time.
Here's the solution I've been using for now. It's a bit of a hack, but hell... It does the job.
First, I get the raw xml as a string instead of reading it as a file:
public void modifyXML() {
try {
JAXBContext context = JAXBContext.newInstance(Assets.class, Image.class);
Unmarshaller um = context.createUnmarshaller();
// This xml should be formatted in a way that JAXB can work with
String rawXML = getRawXML("/path/to/my.xml");
// Hand the modified xml to the unmarshaller
Assets umAssets = (Assets) um.unmarshal(new StreamSource(new StringReader(rawXML)));
// Do stuff with umAssets
...
} catch (Exception e) {
e.printStackTrace();
}
}
private String getRawXML(String path) throws IOException {
String xml = readFile(path, StandardCharsets.UTF_8);
// This should make it so JAXB can handle the xml properly
xml = xml.replaceAll("<main-asset-name>", "<image>");
xml = xml.replaceAll("</main-asset-name>", "</image>");
xml = xml.replaceAll("<product-image.*>", "<image>");
return xml.replaceAll("</product-image.*>", "</image>");
}
private String readFile(String path, Charset encoding) throws IOException {
// A simple file reader
byte[] encoded = Files.readAllBytes(Paths.get(path));
return new String(encoded, encoding);
}
This seems to do the trick, so I'm going to use this for now. Of course, I'm still open to better solutions if anyone has one.
How can I create mime-atachment text/xml for my SOAPMessage?
I have a function, which sends binary file of XML. But I don't know how can I do it.
Use a DataHandler/DataSource to push the binary data into the message on the client side.
On the server side, you need to create a DataContentHandler implementation and register it with the activation framework.
Step 1 - Adding the binary attachment
Implement a simple DataSource for getting the data:
import javax.activation.*;
class BinaryDataSource implements DataSource {
InputStream _is;
public BinaryDataSource(InputStream is) {
_is = is;
}
public String getContentType() { return "application/binary"; }
public InputStream getInputStream() throws IOException { return _is; }
public String getName() { return "some file"; }
public OutputStream getOutputStream() throws IOException {
throw new IOException("Cannot write to this file");
}
}
Now use this code to add the attachment:
InputStream data = ...
SOAPMessage msg = ...
DataHandler dh = new DataHandler(new BinaryDataSource(data));
AttachmentPart attachment = msg.createAttachmentPart(dh);
msg.addAttachmentPart(attachment);
Step 2 - Setup the server side
[Note: this worked for me]
Create a DataContentHandler which handles the incoming attachment of type "application/binary".
import javax.activation.*;
import java.io.*;
public class BinaryDataHandler implements DataContentHandler {
/** Creates a new instance of BinaryDataHandler */
public BinaryDataHandler() {
}
/** This is the key, it just returns the data uninterpreted. */
public Object getContent(javax.activation.DataSource dataSource) throws java.io.IOException {
System.out.println("BinaryDataHandler: getContent called with: " + dataSource);
return dataSource.getInputStream();
}
public Object getTransferData(java.awt.datatransfer.DataFlavor dataFlavor,
javax.activation.DataSource dataSource)
throws java.awt.datatransfer.UnsupportedFlavorException,
java.io.IOException {
return null;
}
public java.awt.datatransfer.DataFlavor[] getTransferDataFlavors() {
return new java.awt.datatransfer.DataFlavor[0];
}
public void writeTo(Object obj, String str, java.io.OutputStream outputStream)
throws java.io.IOException {
// You would need to implement this to have
// the conversion done automatically based on
// mime type on the client side.
}
}
Now, you can use this code to get the data of the attachment:
SOAPMessage msg = ... //received message
Iterator ats = msg.getAttachments();
if( ats.hasNext() ){
AttachmentPart attachment = (AttachmentPart)ats.next();
InputStream contents = (InputStream)attachment.getContent();
}
Finally, you need to register your DataContentHandler so that the activation framework will use it. There are a couple of ways (see MailcapCommandMap in the activation framework javadocs). What I did was to create a file called "mailcap" in the lib directory used by my "java" interpreter.
This file looks like this:
application/binary; BinaryDataHandler
application/binary;; x-java-content-handler=BinaryDataHandler
This tells the activation framework to use your handler for the indicated
MIME type.
How to prepare XML with CDATA ,
I am preraring this response via Jaxb,
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<SOAP-ENV:Header/>
<soapenv:Body>
<tem:RequestData>
<tem:requestDocument>
<![CDATA[
<Request>
<Authentication CMId="68" Function="1" Guid="5594FB83-F4D4-431F-B3C5-EA6D7A8BA795" Password="poihg321TR"/>
<Establishment Id="4297867"/>
</Request>
]]>
</tem:requestDocument>
</tem:RequestData>
</soapenv:Body>
</soapenv:Envelope>
But from Jaxb i am not getting CDATA , how to put CDATA inside <tem:requestDocument> element.
Here is my Java Code :
public static String test1() {
try {
initJB();
String response = null;
StringBuffer xmlStr = null;
String strTimeStamp = null;
com.cultagent4.travel_republic.gm.Envelope envelope = null;
com.cultagent4.travel_republic.gm.Header header = null;
com.cultagent4.travel_republic.gm.Body body = null;
com.cultagent4.travel_republic.gm.RequestData requestData = null;
com.cultagent4.travel_republic.gm.RequestDocument requestDocument = null;
com.cultagent4.travel_republic.gm.RequestDocument.Request request = null;
com.cultagent4.travel_republic.gm.RequestDocument.Request.Authentication authentication = null;
com.cultagent4.travel_republic.gm.RequestDocument.Request.Establishment establishment = null;
ObjectFactory objFact = new ObjectFactory();
envelope = objFact.createEnvelope();
header = objFact.createHeader();
envelope.setHeader(header);
body = objFact.createBody();
requestData = objFact.createRequestData();
requestDocument = objFact.createRequestDocument();
request = new RequestDocument.Request();
authentication = new RequestDocument.Request.Authentication();
authentication.setCMId("68");
authentication.setGuid("5594FB83-F4D4-431F-B3C5-EA6D7A8BA795");
authentication.setPassword("poihg321TR");
authentication.setFunction("1");
request.setAuthentication(authentication);
establishment = new RequestDocument.Request.Establishment();
establishment.setId("4297867");
request.setEstablishment(establishment);
requestDocument.setRequest(request);
requestData.setRequestDocument(requestDocument);
body.setRequestData(requestData);
envelope.setBody(body);
jaxbMarshallerForBase = jaxbContextForBase.createMarshaller();
OutputStream os = new ByteArrayOutputStream();
System.out.println();
// output pretty printed
// jaxbMarshallerForBase.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// jaxbMarshallerForBase.marshal(envelope, System.out);
// jaxbMarshallerForBase.marshal(envelope, os);
jaxbMarshallerForBase.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
jaxbMarshallerForBase.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// jaxbMarshallerForBase.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, false);
// get an Apache XMLSerializer configured to generate CDATA
XMLSerializer serializer = getXMLSerializer();
// marshal using the Apache XMLSerializer
SAXResult result = new SAXResult(serializer.asContentHandler());
System.out.println("*************");
jaxbMarshallerForBase.marshal(envelope, result);
System.out.println("--------------");
return null;
} catch (JAXBException ex) {
Logger.getLogger(GM_TravelRepublic.class.getName()).log(Level.SEVERE, null, ex);
} finally {
return null;
}
}
private static XMLSerializer getXMLSerializer() {
// configure an OutputFormat to handle CDATA
OutputFormat of = new OutputFormat();
// specify which of your elements you want to be handled as CDATA.
// The use of the ; '^' between the namespaceURI and the localname
// seems to be an implementation detail of the xerces code.
// When processing xml that doesn't use namespaces, simply omit the
// namespace prefix as shown in the third CDataElement below.
of.setCDataElements(new String[]{"^Request","^Authentication","^Establishment"});
// set any other options you'd like
of.setPreserveSpace(true);
of.setIndenting(true);
StringWriter writer = new StringWriter();
// create the serializer
XMLSerializer serializer = new XMLSerializer(of);
serializer.setOutputByteStream(System.out);
return serializer;
}
Here I am getting same xml , but without CDATA. My server is not accepting the request without CDATA.Please help.
Can you make the logic from this
imports
import org.dom4j.CDATA;
import org.dom4j.DocumentHelper;
sample code
public static String appendCdata(String input) {
CDATA cdata = DocumentHelper.createCDATA(input);
return cdata.asXML();
}
You need to create an custom adapter class which extends the XMLAdapter class.
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class CDATAAdapter extends XmlAdapter<String, String> {
#Override
public String marshal(String inStr) throws Exception {
return "<![CDATA[" + inStr + "]]>";
}
#Override
public String unmarshal(String v) throws Exception {
return inStr;
}
}
Inside your Java Bean or POJO define XMLJavaTypeAdapter on the string required in CDATA
#XmlJavaTypeAdapter(value=CDATAAdapter.class)
private String message;
By default, the marshaller implementation of the JAXB RI tries to escape characters. To change this behaviour we write a class that
implements the CharacterEscapeHandler.
This interface has an escape method that needs to be overridden.
import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler;
m.setProperty("com.sun.xml.internal.bind.characterEscapeHandler",
new CharacterEscapeHandler() {
#Override
public void escape(char[] ch, int start, int length,
boolean isAttVal, Writer writer)
throws IOException {
writer.write(ch, start, length);
}
});
Secondly, it cn also be done via Eclipse MOXy implementation.
CDATA is character data, it looks like your server wants the part of the XML starting with Request to come in as text. It may be enough for you to create an XmlAdapter to convert the instance of Request to a String. The resulting characters will be escaped not in CDATA but this May fit your use case.
Then if you really need it as CDATA in addition to the XmlAdapter you can apply one of the strategies described in the link below:
How to generate CDATA block using JAXB?
From the setCDataElements method description in the Apache docs :
Sets the list of elements for which text node children should be output as CDATA.
What I think that means is, the children of the tem:requestDocument element should all be part of one single text chunk (and not xml elements by themselves) in order for this to work. Once you've done that, probably a simple
of.setCDataElements(new String[]{"tem^requestDocument"});
should do the trick.
Try it and let me know :)
I think that in your private static XMLSerializer getXMLSerializer() method you are setting wrong the CDATA elements, because your CDATA element is <tem:requestDocument> instead of Request Authentication and Establishment which are the content. Try with:
of.setCDataElements(new String[]{"tem^requestDocument","http://tempuri.org/^requestDocument","requestDocument"});
instead of:
of.setCDataElements(new String[]{"^Request","^Authentication","^Establishment"});
Hope this helps,
Your server is expecting <tem:requestDocument> to contain text, and not
a <Request> element. CDATA is really just helpful for creating hand-written
XML so you don't have to worry about escaping embedded XML. The thing is,
JAXB handles escaping just fine and if your server is a good XML citizen it should
treat properly escaped XML the same as XML in a CDATA block.
So, instead of adding a request element inside your requestDocument like
you do in:
requestDocument = objFact.createRequestDocument();
request = new RequestDocument.Request();
...
requestDocument.setRequest(request);
You should first use JAXB to marshal request into a properly escaped String
and set that sa the requestDocument value:
requestDocument = objFact.createRequestDocument();
request = new RequestDocument.Request();
...
String escapedRequest = marshal(request);
requestDocument.setRequest(escapedRequest);
Implementing marshal(request) is left as an exercise. ;)
I have to convert docx file format (which is in openXML format) into JSON format. I need some guidelines to do it. Thanks in advance.
You may take a look at the Json-lib Java library, that provides XML-to-JSON conversion.
String xml = "<hello><test>1.2</test><test2>123</test2></hello>";
XMLSerializer xmlSerializer = new XMLSerializer();
JSON json = xmlSerializer.read( xml );
If you need the root tag too, simply add an outer dummy tag:
String xml = "<hello><test>1.2</test><test2>123</test2></hello>";
XMLSerializer xmlSerializer = new XMLSerializer();
JSON json = xmlSerializer.read("<x>" + xml + "</x>");
There is no direct mapping between XML and JSON; XML carries with it type information (each element has a name) as well as namespacing. Therefore, unless each JSON object has type information embedded, the conversion is going to be lossy.
But that doesn't necessarily matter. What does matter is that the consumer of the JSON knows the data contract. For example, given this XML:
<books>
<book author="Jimbo Jones" title="Bar Baz">
<summary>Foo</summary>
</book>
<book title="Don't Care" author="Fake Person">
<summary>Dummy Data</summary>
</book>
</books>
You could convert it to this:
{
"books": [
{ "author": "Jimbo Jones", "title": "Bar Baz", "summary": "Foo" },
{ "author": "Fake Person", "title": "Don't Care", "summary": "Dummy Data" },
]
}
And the consumer wouldn't need to know that each object in the books collection was a book object.
Edit:
If you have an XML Schema for the XML and are using .NET, you can generate classes from the schema using xsd.exe. Then, you could parse the source XML into objects of these classes, then use a DataContractJsonSerializer to serialize the classes as JSON.
If you don't have a schema, it will be hard getting around manually defining your JSON format yourself.
The XML class in the org.json namespace provides you with this functionality.
You have to call the static toJSONObject method
Converts a well-formed (but not necessarily valid) XML string into a JSONObject. Some information may be lost in this transformation because JSON is a data format and XML is a document format. XML uses elements, attributes, and content text, while JSON uses unordered collections of name/value pairs and arrays of values. JSON does not does not like to distinguish between elements and attributes. Sequences of similar elements are represented as JSONArrays. Content text may be placed in a "content" member. Comments, prologs, DTDs, and <[ [ ]]> are ignored.
If you are dissatisfied with the various implementations, try rolling your own. Here is some code I wrote this afternoon to get you started. It works with net.sf.json and apache common-lang:
static public JSONObject readToJSON(InputStream stream) throws Exception {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
SAXParser parser = factory.newSAXParser();
SAXJsonParser handler = new SAXJsonParser();
parser.parse(stream, handler);
return handler.getJson();
}
And the SAXJsonParser implementation:
package xml2json;
import net.sf.json.*;
import org.apache.commons.lang.StringUtils;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
import java.util.List;
public class SAXJsonParser extends DefaultHandler {
static final String TEXTKEY = "_text";
JSONObject result;
List<JSONObject> stack;
public SAXJsonParser(){}
public JSONObject getJson(){return result;}
public String attributeName(String name){return "#"+name;}
public void startDocument () throws SAXException {
stack = new ArrayList<JSONObject>();
stack.add(0,new JSONObject());
}
public void endDocument () throws SAXException {result = stack.remove(0);}
public void startElement (String uri, String localName,String qName, Attributes attributes) throws SAXException {
JSONObject work = new JSONObject();
for (int ix=0;ix<attributes.getLength();ix++)
work.put( attributeName( attributes.getLocalName(ix) ), attributes.getValue(ix) );
stack.add(0,work);
}
public void endElement (String uri, String localName, String qName) throws SAXException {
JSONObject pop = stack.remove(0); // examine stack
Object stashable = pop;
if (pop.containsKey(TEXTKEY)) {
String value = pop.getString(TEXTKEY).trim();
if (pop.keySet().size()==1) stashable = value; // single value
else if (StringUtils.isBlank(value)) pop.remove(TEXTKEY);
}
JSONObject parent = stack.get(0);
if (!parent.containsKey(localName)) { // add new object
parent.put( localName, stashable );
}
else { // aggregate into arrays
Object work = parent.get(localName);
if (work instanceof JSONArray) {
((JSONArray)work).add(stashable);
}
else {
parent.put(localName,new JSONArray());
parent.getJSONArray(localName).add(work);
parent.getJSONArray(localName).add(stashable);
}
}
}
public void characters (char ch[], int start, int length) throws SAXException {
JSONObject work = stack.get(0); // aggregate characters
String value = (work.containsKey(TEXTKEY) ? work.getString(TEXTKEY) : "" );
work.put(TEXTKEY, value+new String(ch,start,length) );
}
public void warning (SAXParseException e) throws SAXException {
System.out.println("warning e=" + e.getMessage());
}
public void error (SAXParseException e) throws SAXException {
System.err.println("error e=" + e.getMessage());
}
public void fatalError (SAXParseException e) throws SAXException {
System.err.println("fatalError e=" + e.getMessage());
throw e;
}
}
Converting complete docx files into JSON does not look like a good idea, because docx is a document centric XML format and JSON is a data centric format. XML in general is designed to be both, document and data centric. Though it is technical possible to convert document centric XML into JSON, handling the generated data might be overly complex. Try to focus on the actual needed data and convert only that part.
If you need to be able to manipulate your XML before it gets converted to JSON, or want fine-grained control of your representation, go with XStream. It's really easy to convert between: xml-to-object, json-to-object, object-to-xml, and object-to-json. Here's an example from XStream's docs:
XML
<person>
<firstname>Joe</firstname>
<lastname>Walnes</lastname>
<phone>
<code>123</code>
<number>1234-456</number>
</phone>
<fax>
<code>123</code>
<number>9999-999</number>
</fax>
</person>
POJO (DTO)
public class Person {
private String firstname;
private String lastname;
private PhoneNumber phone;
private PhoneNumber fax;
// ... constructors and methods
}
Convert from XML to POJO:
String xml = "<person>...</person>";
XStream xstream = new XStream();
Person person = (Person)xstream.fromXML(xml);
And then from POJO to JSON:
XStream xstream = new XStream(new JettisonMappedXmlDriver());
String json = xstream.toXML(person);
Note: although the method reads toXML() XStream will produce JSON, since the Jettison driver is used.
If you have a valid dtd file for the xml snippet, then you can easily convert xml to json and json to xml using the open source eclipse link jar. Detailed sample JAVA project can be found here: http://www.cubicrace.com/2015/06/How-to-convert-XML-to-JSON-format.html
I have come across a tutorial, hope it helps you.
http://www.techrecite.com/xml-to-json-data-parser-converter
Use
xmlSerializer.setForceTopLevelObject(true)
to include root element in resulting JSON.
Your code would be like this
String xml = "<hello><test>1.2</test><test2>123</test2></hello>";
XMLSerializer xmlSerializer = new XMLSerializer();
xmlSerializer.setForceTopLevelObject(true);
JSON json = xmlSerializer.read(xml);
Docx4j
I've used docx4j before, and it's worth taking a look at.
unXml
You could also check out my open source unXml-library that is available on Maven Central.
It is lightweight, and has a simple syntax to pick out XPaths from your xml, and get them returned as Json attributes in a Jackson ObjectNode.