Java: generic XML reader using reflection - java

So, I have a class XmlReader, that is able to load and return a list of entities of type Client (which is defined by me), from an xml file. But, I also may have an xml file having entities of type "Movie" or any other type, and I want that my XmlReader to be able to read and retrieve that from the file.. I heard something that reflection could be used in order to make a generic XmlReader class, but I do not know how to do that.. can anyone help me? Thanks in advance! My code for the XmlReader class is:
public class XmlReader<ID, T extends MyObject<ID>>
{
private String fileName;
public XmlReader(String fileName)
{
this.fileName = fileName;
}
public List<Client> loadEntities()
{
List<Client> entities = new ArrayList<>();
XmlHelper docXml = new XmlHelper();
Document document = docXml.loadDocument(this.fileName);
document.getDocumentElement().normalize();
Element root = document.getDocumentElement();
NodeList clientElements = root.getElementsByTagName("field");
int id=-1;
String name="";
for (int i=0; i < clientElements.getLength(); i++)
{
Node clientElement = clientElements.item(i);
Element el = (Element) clientElement;
if (clientElement.getNodeType() != Node.ELEMENT_NODE)
{
// ignoring element due to wrong node type
continue;
}
if (el.hasAttribute("name") && el.getAttribute("name").equals("id"))
{
id = Integer.parseInt(el.getAttribute("value"));
}
if (el.hasAttribute("name") && el.getAttribute("name").equals("name"))
{
name = el.getAttribute("value");
}
if (i % 2 ==1)
{
Client newClient = new Client(id, name);
entities.add(newClient);
}
}
return entities;
}
}
And the xml file looks like:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Clients>
<entity class="class Domain.Client">
<field name="name" type="class java.lang.String" value="Liviu"/>
<field name="id" type="class java.lang.Object" value="1"/>
</entity>
</Clients>

If you are extracting the name of the class from within your XML you could do something like:
String className = ... // extract your class name into this
YourClass c = (YourClass) Class.forName(className).newInstance();

Related

convert xml to objects java

I would appreciate your help, how can I match such xmls data in java with different tags, for example can come: <t: UserAttrs>, <ns3: UserAttrs>, <ns37: UserAttrs>
values can be different between "<" and "UserAttrs>"
I need to extract everything that is in the tag
<Rtp xmlns="http://schemas.tranzaxis.com/tran.xsd">
<Tran>
<t:Request InitiatorRid="ECACS2oob" Kind="TProcessA" LifePhase="Single" NetworkRid="V"
OrigTime="2022-07-06T08:04:00" ProcessorRid="Name" ProcessorInstId="1" OriginatorInterfaceId="1308"
xmlns=""
xmlns:ct=""
xmlns:t=""
xmlns:tc="">
<t:Parties>
<t:Term AcquirerRid="4005"><t:Caps Interactive="true"/><t:Owner Country="0" Rid="test" Title="test" Url="https://xxx" Mcc="3000"/>
</t:Term>
</t:Parties>
<t:UserAttrs>
<ct:ParamValue Rid="Reason"> <ct:Val>Authe</ct:Val>
</ct:ParamValue>
<ct:ParamValue Rid="EndTime"> <ct:Val>2022-07-06T15:04:00.462</ct:Val>
</ct:ParamValue>
</t:UserAttrs>
</t:Request>
</Tran>
You can use javax.xml.bind to unmarshall it and ignore tag namespace. Assume UserAttrs is your object class:
class UserAttrs {
#XmlElement(name = "ParamValue")
private List<ParamValue> paramValue;
}
class ParamValue {
#XmlAttribute(name = "Rid")
private String rId;
}
...
Use unmashaller:
var ctxUserAttrs = JAXBContext.newInstance(UserAttrs.class);
var inputFactory = XMLInputFactory.newInstance();
var reader = inputFactory.createXMLStreamReader(
new ByteArrayInputStream(yourXml.getBytes(StandardCharsets.UTF_8)));
// use reader.hasNext(), reader.next(), reader.nextTag() to navigate to your UserAttrs tag
var unmarshaller = ctxUserAttrs.createUnmarshaller();
var userAttrs = unmarshaller.unmarshal(reader, UserAttrs.class).getValue();
Code is untested, edit to fit your situation. I'm using akarta.xml.bind-api 2.3.3.

XML Parsing - Getting Nodes only from parent with specific ID

I am building a steam-clone game manager in Java and I am having problems with one final part of the project. On the left hand side of the GUI I have automatically populated "playlists" of games that are parsed from a library XML file, and I would like to retrieve the games from only that playlist when it is clicked on through a ListSelectionListener. I am currently able to populate ALL games stored in the library by using getElementsByTagName("Game"), but I need them to be specific to the playlist which is also assigned a unique id with an attributeID set to true for "Id".
However, in the below code, I need to do something like readLib.getElementById(id).getChildNodes(); but every time I do so I get a nullpointer exception at that line. Any ideas? I feel like I'm super close.
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
String[] gameArray = null;
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document readLib = builder.parse(LIBRARY_FILE_PATH);
System.out.println("ID:" + id);
NodeList gameNodes = readLib.getElementsByTagName("Game");
gameArray = new String[gameNodes.getLength()];
for (int i = 0; i < gameNodes.getLength(); i++) {
Node p = gameNodes.item(i);
if (p.getNodeType() == Node.ELEMENT_NODE) {
Element Game = (Element) p;
String gameNames = Game.getAttribute("Name");
gameArray[i] = gameNames;
}
}
} catch (ParserConfigurationException e) {
LogToFile.writeFile("[GM-Logging] Parser configuratoin exception when generating game list");
} catch (SAXException e) {
LogToFile.writeFile("[GM-Logging] General SAXException in generateGames() method");
} catch (IOException e) {
LogToFile.writeFile("[GM-Logging] IOException while generating game names in Library Manager engine");
}
This is an example of what the XML library looks like:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Library>
<Playlist Id="0" list="First Person Shooters">
<Game Name="Counter-Strike: Source">
<Path>C:\\Program Files\\Games\\CSS\cstrike.exe</Path>
<Executable>cstrike.exe</Executable>
</Game>
<Game Name="Counter-Strike: Global Offense">
<Path>C:\\Program Files\\Games\\CSGO\csgo.exe</Path>
<Executable>csgo.exe</Executable>
</Game>
<Game Name="Crysis 3">
<Path>C:\\Program Files\\Games\\Crytek\crysislauncher.exe</Path>
<Executable>crysislauncher.exe</Executable>
</Game>
</Playlist>
<Playlist Id="1" list="Grand Theft Auto Series">
<Game Name="Grand Theft Auto V">
<Path>C:\\Program Files\\Games\\Rockstar\gtav.exe</Path>
<Executable>gtav.exe</Executable>
</Game>
<Game Name="Grand Theft Auto IV: Ballad of Gay Tony">
<Path>C:\\Program Files\\Games\\Rockstar\gtaiv\gtaiv.exe</Path>
<Executable>gtaiv.exe</Executable>
</Game>
</Playlist>
<Playlist Id="2" list="Survival and Horror Games"></Playlist>
I'd suggest to look into xpath. That will help you get specific part of an XML especially when you have more complex filter than just id attribute later. I'm not a java guy, the following codes constructed based on this : How to read XML using XPath in Java :
DocumentBuilder builder = factory.newDocumentBuilder();
Document readLib = builder.parse(LIBRARY_FILE_PATH);
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
XPathExpression expr = xpath.compile("//Playlist[#id='" + id + "']/Game");
System.out.println("ID:" + id);
NodeList gameNodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
//the rest can be the same code
.......
short explanation about xpath being used :
Above codes generate xpath expression that looks like this :
//Playlist[#id='certain_id']/Game
//Playlist : find <Playlist> elements anywhere in the XML document
[#id='certain_id'] : filter to return only those having id attribute equals "certain_id"
/Game : from each of <Playlist> element that satisfies above criterion, get child element <Game>
Looking at your xml, below is my suggestion.
create a class,
#XmlRootElement(name = "Library")
public class Library {
private List<Playlist> playlist = new ArrayList<Playlist>();
..... getter
#XmlElement(name = "Playlist")
public void setPlaylist(List<Playlist> playlist) {
this.playlist = playlist;
}
#XmlRootElement(name = "Playlist")
public class Playlist {
private Game game;
private String path;
private String executable;
#XmlElement(name = "path")
public void setPath(String path) {
this.path = path;
}
... similarly write getter and setter for game & executable
}
#XmlRootElement(name = "Game")
public class Game {
private String name;
#XmlElement(name = "name")
public void setName(String name) {
this.name = name;
}
... write getter
}
in your main class
JAXBContext jaxbContext = JAXBContext.newInstance(Library.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
String xmlStr = xmlString; // the entire xml as string
InputStream is = new ByteArrayInputStream(xmlStr.getBytes());
Library library = (Library) jaxbUnmarshaller.unmarshal(is);
List<Playlist> playLst = library.getPlayList();
now you have playLst object which is parsed XML

How to generate multiple, slightly different XSD schemas from one Java model with JAXB?

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);
}
}
}

Extracting XML Elements from Java Object [duplicate]

I am new to XML. I want to read the following XML on the basis of request name. Please help me on how to read the below XML in Java -
<?xml version="1.0"?>
<config>
<Request name="ValidateEmailRequest">
<requestqueue>emailrequest</requestqueue>
<responsequeue>emailresponse</responsequeue>
</Request>
<Request name="CleanEmail">
<requestqueue>Cleanrequest</requestqueue>
<responsequeue>Cleanresponse</responsequeue>
</Request>
</config>
If your XML is a String, Then you can do the following:
String xml = ""; //Populated XML String....
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(new StringReader(xml)));
Element rootElement = document.getDocumentElement();
If your XML is in a file, then Document document will be instantiated like this:
Document document = builder.parse(new File("file.xml"));
The document.getDocumentElement() returns you the node that is the document element of the document (in your case <config>).
Once you have a rootElement, you can access the element's attribute (by calling rootElement.getAttribute() method), etc. For more methods on java's org.w3c.dom.Element
More info on java DocumentBuilder & DocumentBuilderFactory. Bear in mind, the example provided creates a XML DOM tree so if you have a huge XML data, the tree can be huge.
Related question.
Update Here's an example to get "value" of element <requestqueue>
protected String getString(String tagName, Element element) {
NodeList list = element.getElementsByTagName(tagName);
if (list != null && list.getLength() > 0) {
NodeList subList = list.item(0).getChildNodes();
if (subList != null && subList.getLength() > 0) {
return subList.item(0).getNodeValue();
}
}
return null;
}
You can effectively call it as,
String requestQueueName = getString("requestqueue", element);
In case you just need one (first) value to retrieve from xml:
public static String getTagValue(String xml, String tagName){
return xml.split("<"+tagName+">")[1].split("</"+tagName+">")[0];
}
In case you want to parse whole xml document use JSoup:
Document doc = Jsoup.parse(xml, "", Parser.xmlParser());
for (Element e : doc.select("Request")) {
System.out.println(e);
}
If you are just looking to get a single value from the XML you may want to use Java's XPath library. For an example see my answer to a previous question:
How to use XPath on xml docs having default namespace
It would look something like:
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
public class Demo {
public static void main(String[] args) {
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = domFactory.newDocumentBuilder();
Document dDoc = builder.parse("E:/test.xml");
XPath xPath = XPathFactory.newInstance().newXPath();
Node node = (Node) xPath.evaluate("/Request/#name", dDoc, XPathConstants.NODE);
System.out.println(node.getNodeValue());
} catch (Exception e) {
e.printStackTrace();
}
}
}
There are a number of different ways to do this. You might want to check out XStream or JAXB. There are tutorials and the examples.
If the XML is well formed then you can convert it to Document. By using the XPath you can get the XML Elements.
String xml = "<stackusers><name>Yash</name><age>30</age></stackusers>";
Form XML-String Create Document and find the elements using its XML-Path.
Document doc = getDocument(xml, true);
public static Document getDocument(String xmlData, boolean isXMLData) throws Exception {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
dbFactory.setNamespaceAware(true);
dbFactory.setIgnoringComments(true);
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc;
if (isXMLData) {
InputSource ips = new org.xml.sax.InputSource(new StringReader(xmlData));
doc = dBuilder.parse(ips);
} else {
doc = dBuilder.parse( new File(xmlData) );
}
return doc;
}
Use org.apache.xpath.XPathAPI to get Node or NodeList.
System.out.println("XPathAPI:"+getNodeValue(doc, "/stackusers/age/text()"));
NodeList nodeList = getNodeList(doc, "/stackusers");
System.out.println("XPathAPI NodeList:"+ getXmlContentAsString(nodeList));
System.out.println("XPathAPI NodeList:"+ getXmlContentAsString(nodeList.item(0)));
public static String getNodeValue(Document doc, String xpathExpression) throws Exception {
Node node = org.apache.xpath.XPathAPI.selectSingleNode(doc, xpathExpression);
String nodeValue = node.getNodeValue();
return nodeValue;
}
public static NodeList getNodeList(Document doc, String xpathExpression) throws Exception {
NodeList result = org.apache.xpath.XPathAPI.selectNodeList(doc, xpathExpression);
return result;
}
Using javax.xml.xpath.XPathFactory
System.out.println("javax.xml.xpath.XPathFactory:"+getXPathFactoryValue(doc, "/stackusers/age"));
static XPath xpath = javax.xml.xpath.XPathFactory.newInstance().newXPath();
public static String getXPathFactoryValue(Document doc, String xpathExpression) throws XPathExpressionException, TransformerException, IOException {
Node node = (Node) xpath.evaluate(xpathExpression, doc, XPathConstants.NODE);
String nodeStr = getXmlContentAsString(node);
return nodeStr;
}
Using Document Element.
System.out.println("DocumentElementText:"+getDocumentElementText(doc, "age"));
public static String getDocumentElementText(Document doc, String elementName) {
return doc.getElementsByTagName(elementName).item(0).getTextContent();
}
Get value in between two strings.
String nodeVlaue = org.apache.commons.lang.StringUtils.substringBetween(xml, "<age>", "</age>");
System.out.println("StringUtils.substringBetween():"+nodeVlaue);
Full Example:
public static void main(String[] args) throws Exception {
String xml = "<stackusers><name>Yash</name><age>30</age></stackusers>";
Document doc = getDocument(xml, true);
String nodeVlaue = org.apache.commons.lang.StringUtils.substringBetween(xml, "<age>", "</age>");
System.out.println("StringUtils.substringBetween():"+nodeVlaue);
System.out.println("DocumentElementText:"+getDocumentElementText(doc, "age"));
System.out.println("javax.xml.xpath.XPathFactory:"+getXPathFactoryValue(doc, "/stackusers/age"));
System.out.println("XPathAPI:"+getNodeValue(doc, "/stackusers/age/text()"));
NodeList nodeList = getNodeList(doc, "/stackusers");
System.out.println("XPathAPI NodeList:"+ getXmlContentAsString(nodeList));
System.out.println("XPathAPI NodeList:"+ getXmlContentAsString(nodeList.item(0)));
}
public static String getXmlContentAsString(Node node) throws TransformerException, IOException {
StringBuilder stringBuilder = new StringBuilder();
NodeList childNodes = node.getChildNodes();
int length = childNodes.getLength();
for (int i = 0; i < length; i++) {
stringBuilder.append( toString(childNodes.item(i), true) );
}
return stringBuilder.toString();
}
OutPut:
StringUtils.substringBetween():30
DocumentElementText:30
javax.xml.xpath.XPathFactory:30
XPathAPI:30
XPathAPI NodeList:<stackusers>
<name>Yash</name>
<age>30</age>
</stackusers>
XPathAPI NodeList:<name>Yash</name><age>30</age>
following links might help
http://labe.felk.cvut.cz/~xfaigl/mep/xml/java-xml.htm
http://developerlife.com/tutorials/?p=25
http://www.java-samples.com/showtutorial.php?tutorialid=152
There are two general ways of doing that. You will either create a Domain Object Model of that XML file, take a look at this
and the second choice is using event driven parsing, which is an alternative to DOM xml representation. Imho you can find the best overall comparison of these two basic techniques here. Of course there are much more to know about processing xml, for instance if you are given XML schema definition (XSD), you could use JAXB.
There are various APIs available to read/write XML files through Java.
I would refer using StaX
Also This can be useful - Java XML APIs
You can make a class which extends org.xml.sax.helpers.DefaultHandler and call
start_<tag_name>(Attributes attrs);
and
end_<tag_name>();
For it is:
start_request_queue(attrs);
etc.
And then extends that class and implement xml configuration file parsers you want. Example:
...
public void startElement(String uri, String name, String qname,
org.xml.sax.Attributes attrs)
throws org.xml.sax.SAXException {
Class[] args = new Class[2];
args[0] = uri.getClass();
args[1] = org.xml.sax.Attributes.class;
try {
String mname = name.replace("-", "");
java.lang.reflect.Method m =
getClass().getDeclaredMethod("start" + mname, args);
m.invoke(this, new Object[] { uri, (org.xml.sax.Attributes)attrs });
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
catch (NoSuchMethodException e) {
throw new RuntimeException(e); }
catch (java.lang.reflect.InvocationTargetException e) {
org.xml.sax.SAXException se =
new org.xml.sax.SAXException(e.getTargetException());
se.setStackTrace(e.getTargetException().getStackTrace());
}
and in a particular configuration parser:
public void start_Request(String uri, org.xml.sax.Attributes attrs) {
// make sure to read attributes correctly
System.err.println("Request, name="+ attrs.getValue(0);
}
Since you are using this for configuration, your best bet is apache commons-configuration. For simple files it's way easier to use than "raw" XML parsers.
See the XML how-to

Controlling namespace prefixes in JAXB

How does jaxb determine the list of namespace prefix declarations whem marshalling an object? I used xjc to compile java classes for ebics (ebics schema). When I create an instance for an ebicsRequest it looks like this:
<?xml version="1.0" encoding="UTF-16"?>
<ns2:ebicsRequest xmlns:ns2="http://www.ebics.org/H003" Revision="1" Version="H003" xmlns="http://www.ebics.org/H003" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ns4="http://www.ebics.org/S001" xmlns:ns5="http://www.ebics.org/H000">
<ns2:header authenticate="true">
<ns2:static>
<ns2:HostID>SIZBN001</ns2:HostID>
<ns2:Nonce>A5488F43223063171CA0FA59ADC635F0</ns2:Nonce>
<ns2:Timestamp>2009-08-04T08:41:56.967Z</ns2:Timestamp>
<ns2:PartnerID>EBICS</ns2:PartnerID>
<ns2:UserID>EBIX</ns2:UserID>
<ns2:Product Language="de">EBICS-Kernel V2.0.4, SIZ/PPI</ns2:Product>
<ns2:OrderDetails>
<ns2:OrderType>FTB</ns2:OrderType>
<ns2:OrderID>A037</ns2:OrderID>
<ns2:OrderAttribute>OZHNN</ns2:OrderAttribute>
<ns2:StandardOrderParams/>
</ns2:OrderDetails>
<ns2:BankPubKeyDigests>
<ns2:Authentication Algorithm="RSA" Version="X002">...</ns2:Authentication>
<ns2:Encryption Algorithm="RSA" Version="E002">...</ns2:Encryption>
</ns2:BankPubKeyDigests>
<ns2:SecurityMedium>0000</ns2:SecurityMedium>
<ns2:NumSegments>1</ns2:NumSegments>
</ns2:static>
<ns2:mutable>
<ns2:TransactionPhase>Initialisation</ns2:TransactionPhase>
</ns2:mutable>
</ns2:header>
<ns2:AuthSignature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#xpointer(//*[#authenticate='true'])">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>CSbjPbiNcFqSl6lCI1weK5x1nMeCH5bTQq5pedq5uI0=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>...</ds:SignatureValue>
</ns2:AuthSignature>
<ns2:body>
<ns2:DataTransfer>
<ns2:DataEncryptionInfo authenticate="true">
<ns2:EncryptionPubKeyDigest Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" Version="E002">dFAYe281vj9NB7w+VoWIdfHnjY9hNbZLbHsDOu76QAE=</ns2:EncryptionPubKeyDigest>
<ns2:TransactionKey>...</ns2:TransactionKey>
</ns2:DataEncryptionInfo>
<ns2:SignatureData authenticate="true">...</ns2:SignatureData>
</ns2:DataTransfer>
</ns2:body>
</ns2:ebicsRequest>
I have used a custom NamespacePrefixMapper to declare the default namespace and prefixes for ds and xsi. For the namespace ds it works fine. But for the default namespace it does not. It is declared two times once as ns2 and once as "" the latter coming from my custom NamespacePrefixMapper.getPreDeclaredNamespaceUris. I have played around a lot with this class. Also I tried to use the package-info.java but I was not able to make jaxb use "http://www.ebics.org/H003" as default namespace. What I also do not understand is the appearance of ns4 and ns5 which are not at all part of the xml document.
My NamespacePrefixMapper class looks like
public class NamespacePrefixMapperImpl extends NamespacePrefixMapper implements NamespaceContext {
private static final String[] EMPTY_STRING = new String[0];
private Map prefixToUri = null;
private Map uriToPrefix = null;
private void init(){
prefixToUri = new HashMap();
prefixToUri.put("", "http://www.ebics.org/H003" );
prefixToUri.put("ds", "http://www.w3.org/2000/09/xmldsig#" );
prefixToUri.put("xsi", "http://www.w3.org/2001/XMLSchema-instance" );
prefixToUri.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI );
prefixToUri.put(XMLConstants.XMLNS_ATTRIBUTE , XMLConstants.XMLNS_ATTRIBUTE_NS_URI );
uriToPrefix = new HashMap();
for(String prefix : prefixToUri.keySet()){
uriToPrefix.put(prefixToUri.get(prefix), prefix);
}
}
#Override
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
if (uriToPrefix == null)
init();
if (uriToPrefix.containsKey(namespaceUri)){
return uriToPrefix.get(namespaceUri);
}
return suggestion;
}
#Override
public String[] getContextualNamespaceDecls() {
// TODO Auto-generated method stub
return EMPTY_STRING;
}
#Override
public String[] getPreDeclaredNamespaceUris() {
// TODO Auto-generated method stub
return EMPTY_STRING;
}
#Override
public String[] getPreDeclaredNamespaceUris2() {
return new String [] {"", prefixToUri.get("")};
}
public String getNamespaceURI(String prefix) {
if (prefixToUri == null)
init();
if (prefixToUri.containsKey(prefix)) {
return prefixToUri.get(prefix);
} else {
return XMLConstants.NULL_NS_URI;
}
}
public String getPrefix(String namespaceURI) {
if (uriToPrefix == null)
init();
if (uriToPrefix.containsKey(namespaceURI)){
return uriToPrefix.get(namespaceURI);
} else {
return null;
}
}
public Iterator getPrefixes(String namespaceURI) {
if (uriToPrefix == null)
init();
List prefixes = new LinkedList();
if (uriToPrefix.containsKey(namespaceURI)){
prefixes.add(uriToPrefix.get(namespaceURI));
}
return prefixes.iterator();
}
}
I am using
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.6.5
Created-By: 1.5.0-b64 (Sun Microsystems Inc.)
Specification-Title: Java Architecture for XML Binding
Specification-Version: 2.0
Specification-Vendor: Sun Microsystems, Inc.
Implementation-Title: JAXB Reference Implementation
Implementation-Version: 2.0.2
Implementation-Vendor: Sun Microsystems, Inc.
Implementation-Vendor-Id: com.sun
Extension-Name: com.sun.xml.bind
Build-Id: b01
Class-Path: jaxb-api.jar activation.jar jsr173_1.0_api.jar jaxb1-impl.
jar
Name: com.sun.xml.bind.v2.runtime
Implementation-Version: 2.0.2-b01-fcs
JAXB always adds all namespaces that are known by the JAXBContext to the root element of the XML document for performance reasons. See this comment by Kohsuke on JAXB-103 for more information.
The only way I found to deal with this, is to traverse the document myself after it has been created with JAXB and remove all unused namespaces using the following helper class:
public class RemoveUnusedNamespaces {
private static final String XML_NAMESPACE_SCHEMA_INSTANCE = "http://www.w3.org/2001/XMLSchema-instance";
private static final String XML_NAMESPACE_NAMESPACE = "http://www.w3.org/2000/xmlns/";
private interface ElementVisitor {
void visit(Element element);
}
public void process(Document document) {
final Set<String> namespaces = new HashSet<String>();
Element element = document.getDocumentElement();
traverse(element, new ElementVisitor() {
public void visit(Element element) {
String namespace = element.getNamespaceURI();
if (namespace == null)
namespace = "";
namespaces.add(namespace);
NamedNodeMap attributes = element.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
if (XML_NAMESPACE_NAMESPACE.equals(node.getNamespaceURI()))
continue;
String prefix;
if (XML_NAMESPACE_SCHEMA_INSTANCE.equals(node.getNamespaceURI())) {
if ("type".equals(node.getLocalName())) {
String value = node.getNodeValue();
if (value.contains(":"))
prefix = value.substring(0, value.indexOf(":"));
else
prefix = null;
} else {
continue;
}
} else {
prefix = node.getPrefix();
}
namespace = element.lookupNamespaceURI(prefix);
if (namespace == null)
namespace = "";
namespaces.add(namespace);
}
}
});
traverse(element, new ElementVisitor() {
public void visit(Element element) {
Set<String> removeLocalNames = new HashSet<String>();
NamedNodeMap attributes = element.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
if (!XML_NAMESPACE_NAMESPACE.equals(node.getNamespaceURI()))
continue;
if (namespaces.contains(node.getNodeValue()))
continue;
removeLocalNames.add(node.getLocalName());
}
for (String localName : removeLocalNames)
element.removeAttributeNS(XML_NAMESPACE_NAMESPACE, localName);
}
});
}
private final void traverse(Element element, ElementVisitor visitor) {
visitor.visit(element);
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() != Node.ELEMENT_NODE)
continue;
traverse((Element) node, visitor);
}
}
}
EclipseLink JAXB (MOXy) uses the prefixes as specified in the #XmlSchema annotation (I'm the MOXy lead). Check out my answer to a similar question for an example:
How to customize namespace prefixes on Jersey(JAX-WS)
The way I've found to get JAXB to remove the ns2 prefix is to include the following attribute in the xs:schema element: elementFormDefault="qualified". So it would look something like this:
<xs:schema targetNamespace="urn:blah:blah" xmlns="urn:blah:blah" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
I generated my JAXB classes using xjc, but the SOAP WebService i am using force me to follow some rules, like not using namespace prefix.
This is invalid:
<envEvento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<idLote>123</idLote>
<evento>
<ns2:Signature/>
</evento>
</envEvento>
This is valid:
<envEvento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>123</idLote>
<evento>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"/>
</evento>
</envEvento>
As pointed out, JAXB put the namespace declaration at the root element.
To overcome this, the first approach i use is to avoid unnecessary elements in the context.
For example, setting the context of the marshaller like this:
JAXBContext.newInstance("path.to.package");
Can lead JAXB to make some unncessary declarations of namespaces.
Sometimes, i can get ride of an annoying xmlns="http://www.w3.org/2000/09/xmldsig#" just setting the context with the necessary Root Element:
JAXBContext.newInstance(MyRootElement.class);
A second approach i use when the first is not enough, is to make the entire context use the same namespace. Just changing the unwanted "http://www.w3.org/2000/09/xmldsig#", in every namespace declaration (like #XmlElement or #XSchema), to the unique namespace allowed (http://www.portalfiscal.inf.br/nfe)
Then, i just create an attribute at the desired child:
#XmlAttribute(name="xmlns")
String xmlns = "http://www.w3.org/2000/09/xmldsig#";
Now i have the namespace declaration out of the root, in the correct element, without using any prefix.
After crawling many posts, solutions using NamespacePrefixMapper has dependency on JDK version (which may break the code in the future) or the XML DOM tree manipulation looks complicated.
My brute-force approach is to manipulate the generated XML itself.
/**
* Utility method to hide unused xmlns definition in XML root.
* #param sXML Original XML string.
* #return
*/
public static String hideUnUsedNamespace(String sXML) {
int iLoc0 = sXML.indexOf("?><");
int iLoc1 = sXML.indexOf("><",iLoc0+3)+1;
String sBegin = sXML.substring(0,iLoc0+2);
String sHeader = sXML.substring(iLoc0+2, iLoc1-1);
String sRest = sXML.substring(iLoc1);
//System.out.println("sBegin=" + sBegin);
//System.out.println("sHeader=" + sHeader);
//System.out.println("sRest=" + sRest);
String[] saNS = sHeader.split(" ");
//System.out.println("saNS=" + java.util.Arrays.toString(saNS));
StringBuffer sbHeader = new StringBuffer();
for (String s: saNS) {
//System.out.println(s);
if (s.startsWith("xmlns:")) {
String token = "<" + s.substring(6,s.indexOf("="));
//System.out.println("token=" + token + ",indexOf(token)=" + sRest.indexOf(token));
if (sRest.indexOf(token) >= 0) {
sbHeader = sbHeader.append(s).append(" ");
//System.out.println("...included");
}
} else {
sbHeader = sbHeader.append(s).append(" ");
}
}
return (sBegin + sbHeader.toString().trim() + ">" + sRest);
}
/**
* Main method for testing
*/
public static void main(String[] args) {
String sXML ="<?xml version=\"1.0\" encoding=\"UTF-16\"?><ns2:ebicsRequest xmlns:ns2=\"http://www.ebics.org/H003\" Revision=\"1\" Version=\"H003\" xmlns=\"http://www.ebics.org/H003\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:ns4=\"http://www.ebics.org/S001\" xmlns:ns5=\"http://www.ebics.org/H000\"><ns2:header authenticate=\"true\"><ns2:static><ns2:HostID>SIZBN001</ns2:HostID><ns2:Nonce>A5488F43223063171CA0FA59ADC635F0</ns2:Nonce><ns2:Timestamp>2009-08-04T08:41:56.967Z</ns2:Timestamp><ns2:PartnerID>EBICS</ns2:PartnerID><ns2:UserID>EBIX</ns2:UserID><ns2:Product Language=\"de\">EBICS-Kernel V2.0.4, SIZ/PPI</ns2:Product><ns2:OrderDetails><ns2:OrderType>FTB</ns2:OrderType><ns2:OrderID>A037</ns2:OrderID><ns2:OrderAttribute>OZHNN</ns2:OrderAttribute><ns2:StandardOrderParams/></ns2:OrderDetails><ns2:BankPubKeyDigests><ns2:Authentication Algorithm=\"RSA\" Version=\"X002\">...</ns2:Authentication><ns2:Encryption Algorithm=\"RSA\" Version=\"E002\">...</ns2:Encryption></ns2:BankPubKeyDigests><ns2:SecurityMedium>0000</ns2:SecurityMedium><ns2:NumSegments>1</ns2:NumSegments></ns2:static><ns2:mutable><ns2:TransactionPhase>Initialisation</ns2:TransactionPhase></ns2:mutable></ns2:header><ns2:AuthSignature><ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/><ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"/><ds:Reference URI=\"#xpointer(//*[#authenticate='true'])\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"/><ds:DigestValue>CSbjPbiNcFqSl6lCI1weK5x1nMeCH5bTQq5pedq5uI0=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>...</ds:SignatureValue></ns2:AuthSignature><ns2:body><ns2:DataTransfer><ns2:DataEncryptionInfo authenticate=\"true\"><ns2:EncryptionPubKeyDigest Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\" Version=\"E002\">dFAYe281vj9NB7w+VoWIdfHnjY9hNbZLbHsDOu76QAE=</ns2:EncryptionPubKeyDigest><ns2:TransactionKey>...</ns2:TransactionKey></ns2:DataEncryptionInfo><ns2:SignatureData authenticate=\"true\">...</ns2:SignatureData></ns2:DataTransfer></ns2:body></ns2:ebicsRequest>";
System.out.println("Before=" + sXML);
System.out.println("After =" + hideUnUsedNamespace(sXML));
}
The output shows un-used xmlns namespace is filtered out:
<ns2:ebicsRequest xmlns:ns2="http://www.ebics.org/H003" Revision="1" Version="H003" xmlns="http://www.ebics.org/H003" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">

Categories

Resources