Friday, October 21, 2016

Convert XML to Objects / XML to Java Classes

Problem : To integrate an external service with your product if all you have is request or response XML. That is in case you dont have WSDL.

How come this problem?
Such a scenario can occur if you want to integrate a service but dont have licence or provide a possible service hook so it can be used during implementation.


Steps 


1. Convert XML to XSD

I used this (http://xmlgrid.net/xml2xsd.html). There was one from freeformatter.com  but it is not able to generate XSD's properly. 

Remember to choose the XML which is most data rich. Of course avoid any error response XMLs.




2. Preprocess XSD

Now, there are problems with the conversion.

a. Remove namespaces in XSD - if you ignore this step, then JAXB will throw error while converting it in next step.

b. Add any datatypes not present in XSD. Every data must have data type associated with it. 

c. Correct the incorrect datatypes as seen in cases of int - They are double. Sometimes DateTime is interpreted as String. 

d. Remove the last character. It cant be recognised -  will show error in Eclipse.



3. Use Eclipse or xfc provided in JDK.

a. Right click the your XSD in eclipse and choose new -> Generate JAXB Classes from Schema.
There are many tutorials (http://www.javawebtutor.com/articles/jaxb/jaxb_java_class_from_xsd.php)

Provide package for the new classes etc. and you are done.

b. Alternatively
you can use xjc utlity . This file comes along with JDK.


xjc  Request.xsd

Generation of separate classes
JAXB will generate a single class with all other classes as nested static classes.

In case you want separate classes for each complex type in XSD then bindings.xsb will come to your rescue. This file tells JAXB to generate separate classes.

Bindings.xjb
<jaxb:bindings
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    version="1.0">
    <jaxb:globalBindings localScoping="toplevel"/>
</jaxb:bindings>

Bindings is an optional file.
xjc -b bindings.xjb  Request.xsd

Another problem I faced was conflicts with naming.

[ERROR] (Relevant to above error) another "MyType" is generated from here.
  line 93 of file:/C:/temp2/Request.xsd
  
  
There were several classes being generated with the same name.
-XautoNameResolution  - will resolve the issue.


xjc -XautoNameResolution -b bindings.xjb  Request.xsd

Wednesday, October 19, 2016

Rename or Remove Namespace in XML file

Recently while working with web services, I stumbled on a problem where in I had to read the the XMLs and convert them into object.

Now this can be done easily, if you have WSDL but I dont usually encounter easy problems.

All I had was an XML and I have manually create an object for it. However, JAXB unmarshaller wont convert it because of namespaces. Hence, I wrote this program to remove namespaces which can be used to rename namespaces as well.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
  * Recursively renames the namespace of a node.
  * 
  * @param node
  *            the starting node.
  * @param namespace
  *            the new namespace. Supplying <tt>null</tt> removes the namespace.
  */
 public static void renameNamespace(Node node, String namespace) {

  Document document = node.getOwnerDocument();
  if (node.getNodeType() == Node.ELEMENT_NODE) {
   String nodename = node.getNodeName();
   Element e = (Element) node;
   NamedNodeMap attrs = node.getAttributes();
   String attributeName = null;
   //rename the namespace in the tag
   if (nodename.lastIndexOf(":") > 0) {
    nodename = (String) nodename.subSequence(nodename.lastIndexOf(":") + 1, nodename.length());
    nodename = namespace + ":" + nodename;
   }
   document.renameNode(node, namespace, nodename);
  }
  NodeList list = node.getChildNodes();
  //call recursively
  for (int i = 0; i < list.getLength(); ++i) {
   renameNamespace(list.item(i), namespace);
  }
 }

Rename Namespaces



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 /**
  * Recursively removes the namespace of a node.
  * 
  * @param node
  *            the starting node.
  * @param namespace
  *            the new namespace. Supplying <tt>null</tt> removes the namespace.
  */
 public static void removeNamespace(Node node) {

  Document document = node.getOwnerDocument();
  if (node.getNodeType() == Node.ELEMENT_NODE) {
   String nodename = node.getNodeName();
   Element e = (Element) node;
   NamedNodeMap attrs = node.getAttributes();
   String attributeName = null;
   //remove the tag which defines namespace
   for (int i = 0; i < attrs.getLength(); i++) {
    if (attrs.item(i) != null) {
     attributeName = attrs.item(i).getNodeName();
     if (attributeName.startsWith("xmlns")) {
      e.removeAttribute(attrs.item(i).getNodeName());
      i--;
     }
    }
   }
   //remove the namespace in the tag
   if (nodename.lastIndexOf(":") > 0) {
    nodename = (String) nodename.subSequence(nodename.lastIndexOf(":") + 1, nodename.length());
   }
   document.renameNode(node, null, nodename);
  }
  NodeList list = node.getChildNodes();
  //call recursively
  for (int i = 0; i < list.getLength(); ++i) {
   removeNamespace(list.item(i), namespace);
  }
 }

Remove Namespaces




 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 private static void print(PrintStream out, Document doc) throws IOException {

  OutputFormat fmt = new OutputFormat();
  fmt.setIndenting(true);
  XMLSerializer ser = new XMLSerializer(out, fmt);
  ser.serialize(doc);
 }

 public static void main(String[] args) {

  try {
   File fXmlFile = new File("src\\test\\xyz.xml");
   DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
   DocumentBuilder dBuilder;
   dBuilder = dbFactory.newDocumentBuilder();
   Document doc = dBuilder.parse(fXmlFile);
   renameNamespace(doc.getDocumentElement().getParentNode(), "renamed");
   //removeNamespace(doc.getDocumentElement().getParentNode());
   print(System.out, doc);
  } catch (ParserConfigurationException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (SAXException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

Test Code



After removing the namespaces the unmarshaller worked like charm.