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 i
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">
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#">
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.
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: