I have to generate the following format of XML using java:
<document>
<creditDetails>
<accountNo>123456</accountNo>
</creditDetails>
<creditDetails>
<accountNo>123123</accountNo>
</creditDetails>
.
.
.
...
</document>
Now, I have created the above provided XML using the Document.appendChild and relevant XML code.
When I have to create a new block of Credit Details, I have to create a new XML component which is a very bad practice.
I have lets say 15 records and I have iterated those records one by one to create a single xml format for all of them as provided in my question.
Can someone guide me? How I can achieve this?
The correct way would be to use JAXB (Java Architecture for XML Binding).
Then you should have the following data classes:
#XmlRootElement(name = "document")
public class Document {
private List<CreditDetail> creditDetails = new ArrayList<>();
public List<CreditDetail> getCreditDetails() {
return creditDetails;
}
}
public class CreditDetail {
private int accountNo;
public int getAccountNo() {
return accountNo;
}
public void setAccountNo(int accountNo) {
this.accountNo = accountNo;
}
}
Now you can just create your account data in Java and the Marshaller will do the work for you:
Document doc = new Document();
CreditDetail creditDetail1 = new CreditDetail();
creditDetail1.setAccountNo(123456);
doc.getCreditDetails().add(creditDetail1);
CreditDetail creditDetail2 = new CreditDetail();
creditDetail2.setAccountNo(123123);
doc.getCreditDetails().add(creditDetail2);
// could make your classes a bit more pretty, e.g. Factory methods or constructor
JAXBContext context = JAXBContext.newInstance(Document.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter writer = new StringWriter();
marshaller.marhaller(document, writer);
writer.toString() // gives your XML
Unfortunately JAXB is not included in the JDK since Java 11 so you have to add it in your dependency management.
I want to create a generic xmlFileValidator with JAXB, that takes a XML file and a schema file, and returns root class's object if the file is valid else throws SAXException or JAXBException.
public class XmlValidateEventHandler implements ValidationEventHandler {
public static <E> void validator(File xsdFile, File xmlFile, E obj) throws SAXException, JAXBException
{
JAXBContext jaxbcontextobj = JaxbUtil.create_context_obj(new ObjectFactory());
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(xsdFile);
Unmarshaller unmarshaller = JaxbUtil.create_unmarshl_obj(jaxbcontextobj);
unmarshaller.setSchema(schema);
unmarshaller.setEventHandler(new XmlValidateEventHandler());
javax.xml.bind.JAXBElement<Document> doc = (javax.xml.bind.JAXBElement<Document>) unmarshaller.unmarshal(xmlFile);
Document d = doc.getValue();
obj=(E)d;
}
#Override
public boolean handleEvent(ValidationEvent event) {
Logger logger = utility.JaxbUtil.getLogger();
if(event.getSeverity() == ValidationEvent.ERROR || event.getSeverity() == ValidationEvent.FATAL_ERROR)
{
//Logger logger = Logger.getLogger(XmlValidateEventHandler.class);
logger.error("SEVERITY: " + event.getSeverity());
logger.error("MESSAGE: " + event.getMessage());
logger.error("LINKED EXCEPTION: " + event.getLinkedException());
logger.error("LINE NUMBER: " + event.getLocator().getLineNumber());
logger.error("COLUMN NUMBER: " + event.getLocator().getColumnNumber());
logger.error("***** Give XML is invalid aginst given XSD *****");
return false;
}
}
}
The following line gives warning ->
Type safety: Unchecked cast from Object to JAXBElement
javax.xml.bind.JAXBElement<Document> doc=(javax.xml.bind.JAXBElement<Document>) unmarshaller.unmarshal(xmlFile);
And I am also not able to map the unmarshaled object and set it to E obj which is passed in argument.
I know it can returned, but i have been asked to set it into that parameter.
Please help.
Thanks in advance
I changed to following and it works :
public static <E> E validator(File xsdFile, File xmlFile, E obj) throws SAXException, JAXBException
{
JAXBContext jaxbcontextobj = JaxbUtil.setContextObj(new ObjectFactory());
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(xsdFile);
Unmarshaller unmarshaller = JaxbUtil.setUnmarshlObj(jaxbcontextobj);
unmarshaller.setSchema(schema);
unmarshaller.setEventHandler(new XmlValidateEventHandler());
javax.xml.bind.JAXBElement<E> doc = (javax.xml.bind.JAXBElement<E>) unmarshaller.unmarshal(xmlFile);
E d = (E) doc.getValue();
return d;
}
But now there are still open ends :
1. I am still not able not able to map the object to third parameter -> E obj
2. I am using ObjectFactory from JAXB generated classes :
import XmlClasses.ObjectFactory;
Which is still making it not fully generic, how to take a generic ObjectFactory ??
I Have successfully moved the values from my Data.xml to Java class and I tested it by getting the values to Eclipse console . But I am unable to copy the values from java class to my JPA class. Am I missing something? ANy suggestions?
public static void main(String[] args) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Service.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
File XMLfile = new File( "C:\\Users\\Data.xml");
Service Serv = (Service) jaxbUnmarshaller.unmarshal(XMLfile);
System.out.println(model.toString());
MyComponent MyComp = new MyComponent();
JPAComponent JPAcomp = new JPAComponent();
BeanUtils.copyProperties(JPACom, MyComp);
I have a set of related Java classes, which are able to hold data I need. Below is a simplified class diagram of what I have:
Now I need to import data from XML and for that I want to generate XSD schema. The problem is that I want several XSD schemas like this:
One that allows the whole data graph to be imported.
One that allows only RootNote.fieldA and ChildNodeA.
One that allows only RootNote.fieldB and ChildNodeB.
I can easily generate XSD that meets the requirements of nr.1 using JAXB (programmatically). But is there a way to do that for cases nr.2 and nr.3 for the same classes? In other words, it seems I need something like "profiles" in JAXB.
Update:
Here is how I generate XSD schema:
JAXBContext jc = JAXBContext.newInstance(RootNode.class);
final File baseDir = new File(".");
class MySchemaOutputResolver extends SchemaOutputResolver {
public Result createOutput( String namespaceUri, String suggestedFileName ) throws IOException {
return new StreamResult(new File(baseDir,suggestedFileName));
}
}
jc.generateSchema(new MySchemaOutputResolver());
This is not a full answer, just an idea.
You probably use the javax.xml.bind.JAXBContext.generateSchema(SchemaOutputResolver) method to generate your schema, so you basically use a specific JAXBContext instance. This instance is built based on the annotations in classes. When building the context, these annotations are read an organized into a model which is then used for all the operations.
So to generate different schemas you probably need to create different contexts. You can't change the annotations per case, but you can read annotations in different ways.
Take a look at the AnnotationReader. This is what JAXB RI uses behind the scenes to load annotations from Java classes. You can create your own implementation and use it when creating the JAXBContext. Here's an example of something similar:
final AnnotationReader<Type, Class, Field, Method> annotationReader = new AnnoxAnnotationReader();
final Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBRIContext.ANNOTATION_READER, annotationReader);
final JAXBContext context = JAXBContext.newInstance(
"org.jvnet.annox.samples.po",
Thread.currentThread().getContextClassLoader(),
properties);
So how about writing your own annotation reader, which would consider what you call "profiles"? You can invent your own annotation #XmlSchemaProfile(name="foo"). Your annotation reader would then check if this annotation is present with the desired value and then either return it or ignore it. You'll be able to build different contexts from the same Java model - and consequently produce different schemas according to profiles defined by your #XmlSchemaProfile annotations.
I found a solution that suited me. The idea is to output the result of XSD generation into an XML Document (in-memory DOM). JAXB allows that. After this, you can manipulate the document any way you wish, adding or removing parts.
I wrote some filters that whitelist or blacklist fields (in XSD they are elements) and classes (in XSD they are complex types). While I see a lot of potential problems with this approach, it did the job in my case. Below is the code for case 2 schema:
// This SchemaOutputResolver implementation saves XSD into DOM
static class DOMResultSchemaOutputResolver extends SchemaOutputResolver {
private List<DOMResult> results = new LinkedList<DOMResult>();
#Override
public Result createOutput(String ns, String file) throws IOException {
DOMResult result = new DOMResult();
result.setSystemId(file);
results.add(result);
return result;
}
public Document getDocument() {
return (Document)results.get(0).getNode();
}
public String getFilename() {
return results.get(0).getSystemId();
}
}
// This method serializes the DOM into file
protected void serializeXsdToFile(Document xsdDocument, String filename) throws IOException {
OutputFormat format = new OutputFormat(xsdDocument);
format.setIndenting(true);
FileOutputStream os = new FileOutputStream(filename);
XMLSerializer serializer = new XMLSerializer(os, format);
serializer.serialize(xsdDocument);
}
#Test
public void generateSchema2() throws JAXBException, IOException, XPathExpressionException {
JAXBContext context = JAXBContext.newInstance(RootNode.class);
DOMResultSchemaOutputResolver schemaOutputResolver = new DOMResultSchemaOutputResolver();
context.generateSchema(schemaOutputResolver);
// Do your manipulations here as you want. Below is just an example!
filterXsdDocumentComplexTypes(schemaOutputResolver.getDocument(), asList("childNodeA"), true);
filterXsdDocumentElements(schemaOutputResolver.getDocument(), asList("fieldB"));
serializeXsdToFile(schemaOutputResolver.getDocument(), "xf.xsd");
}
private boolean shouldComplexTypeBeDeleted(String complexTypeName, List<String> complexTypes, boolean whitelist) {
return (whitelist && !complexTypes.contains(complexTypeName)) || (!whitelist && complexTypes.contains(complexTypeName));
}
protected void filterXsdDocumentComplexTypes(Document xsdDocument, List<String> complexTypes, boolean whitelist) throws XPathExpressionException {
XPath xPath = XPathFactory.newInstance().newXPath();
NodeList complexTypeNodes = (NodeList)xPath.evaluate("//*[local-name() = 'complexType']", xsdDocument, XPathConstants.NODESET);
for (int i = 0; i < complexTypeNodes.getLength(); i++) {
Node node = complexTypeNodes.item(i);
Node complexTypeNameNode = node.getAttributes().getNamedItem("name");
if (complexTypeNameNode != null) {
if (shouldComplexTypeBeDeleted(complexTypeNameNode.getNodeValue(), complexTypes, whitelist)) {
node.getParentNode().removeChild(node);
}
}
}
NodeList elements = (NodeList)xPath.evaluate("//*[local-name() = 'element']", xsdDocument, XPathConstants.NODESET);
for (int i = 0; i < elements.getLength(); i++) {
Node node = elements.item(i);
Node typeNameNode = node.getAttributes().getNamedItem("type");
if (typeNameNode != null) {
if (shouldComplexTypeBeDeleted(typeNameNode.getNodeValue(), complexTypes, whitelist) && !typeNameNode.getNodeValue().startsWith("xs")) {
node.getParentNode().removeChild(node);
}
}
}
}
protected void filterXsdDocumentElements(Document xsdDocument, List<String> blacklistedElements) throws XPathExpressionException {
XPath xPath = XPathFactory.newInstance().newXPath();
NodeList elements = (NodeList)xPath.evaluate("//*[local-name() = 'element']", xsdDocument, XPathConstants.NODESET);
for (int i = 0; i < elements.getLength(); i++) {
Node node = elements.item(i);
if (blacklistedElements.contains(node.getAttributes().getNamedItem("name").getNodeValue())) {
node.getParentNode().removeChild(node);
}
}
}
I am using the following code to do unmarshalling:
#Override
public String marshal(Object document) throws JAXBException {
Class clazz = document.getClass();
JAXBContext context =
JAXBContext.newInstance( clazz.getPackage().getName() );
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
StringWriter sw = new StringWriter();
marshaller.marshal(document, sw);
String xml = sw.toString();
return xml;
}
The result is like this :
<IlpQuoteInput QuoteId="2888284000185" xmlns="http://www.abc.com">
<Common IlpSellerId="0001">
<Quotation QuotationDt="20130711"/>
<Product CurrencyCd="E">
etc etc
It is all good, but I actually dont want to have the xmlns in the output, what shall I do ?
Thanks
PS I am using latest version of JAXB and java 6.
In your mappings you have most likely supplied an #XmlSchema annotation on a package-info class that looks something like the following:
#XmlSchema(
namespace = "http://www.abc.com",
elementFormDefault = XmlNsForm.QUALIFIED)
package example;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
To remove the the namespace qualification from the XML output you can simply remove the metadata you put in to cause it to happen in the first place, assuming that is what you want to do.
For More Information
http://blog.bdoughan.com/2010/08/jaxb-namespaces.html