10

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
Wilberforce
  • 136
  • 1
  • 1
  • 5
  • 1
    See http://stackoverflow.com/questions/1982977/is-it-possible-to-customize-the-namespace-prefix-that-jaxb-uses-when-marshalling – skaffman Feb 02 '11 at 10:47
  • Could you please attach your NamespacePrefixMapper implementation? – sfussenegger Feb 02 '11 at 11:00
  • Hi skaffman, the problem about using XMLStreamWriter as suggested in the answer for the above question is: I want to marshal into an org.w3c.dom.Document. I was not able to add a proper signature to the jaxb instance of my document by marhalling a fragment of the document and the sign it. The marshalled fragment looks different from the fragment of the overall marshalled document (again because of namespace declarations) so the signature was not valid when marshalling the whole document. So I went for marshalling into a Dom Document and then add the signature and then serialize. :-( – Wilberforce Feb 02 '11 at 11:15

5 Answers5

8

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

}
RD_WF
  • 105
  • 1
  • 9
Reboot
  • 1,716
  • 2
  • 14
  • 27
  • How do you traverse the document a 2nd time? Using string manipulation? – MRalwasser Apr 02 '14 at 10:36
  • No, it is done before the document is serialized by iterating over all child nodes for every element in the DOM tree starting at the root node. – Reboot Apr 02 '14 at 14:08
  • I added a SOAPHandler to my service and called this utility in the handleMessage(SOAPMessageContext context) method and it worked like a charm. Now my CXF webservice strips the unused namespaces and the .NET service it connect to doesn't complain because the root element of the SOAP message is larger than the allowed size for an element declaration, which is only 4000 bytes! Cheers Reboot. – Craig Oct 30 '14 at 07:36
  • Any experience of the impact/performance of this clean up action ? – edbras Jan 01 '16 at 13:04
  • @edbras I did my own personal tests using JMH. I got an average of 78 operations per second without and and 74 operations per second with RemoveUnusedNamespaces added. I was removing about 30 unused namespaces on a small XML document – rjdkolb Apr 21 '16 at 11:01
2

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:

Community
  • 1
  • 1
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • Hi Blaise, thanks for the answer. I tried this solution with my jaxb implementation, see below. My code runs in an JEE environment and I would like to use the jaxb library available. I may check out MOXy if I do not find a solution. @javax.xml.bind.annotation.XmlSchema(namespace = "http://www.ebics.org/H003", xmlns = { @javax.xml.bind.annotation.XmlNs(prefix = "", namespaceURI = "http://www.ebics.org/H003") , @javax.xml.bind.annotation.XmlNs(prefix = "ds", namespaceURI = "http://www.w3.org/2000/09/xmldsig#") }, elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) – Wilberforce Feb 02 '11 at 19:35
  • @Wilberforce - Somehow I missed this comment. If you are using the reference implementation of JAXB you can also use a NamespacePrefixMappiner: http://jaxb.java.net/guide/Changing_prefixes.html – bdoughan Mar 04 '11 at 16:43
  • 2
    Yes, I used NamespacePrefixMappiner (see code above). I now found out that my problem was related to xjc generating java code that had erroneously generated annotations with attribute namespace set to "". That forced jaxb to use "" as default namespace overiding any custom settings. I did not find out why exactly that happened but after changing the code manually All is working fine. – Wilberforce Mar 14 '11 at 18:06
  • 1
    @Wilberforce: You should have done that as an answer to your question so that we can vote it up. (Self-answering is encouraged, provided it is an answer.) – Donal Fellows Sep 28 '11 at 08:58
1

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">
MeplatMasher
  • 88
  • 1
  • 6
1

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.

Gauss
  • 1,108
  • 14
  • 13
  • this is a huge Thor-like hammer, but hey... I was struggling with the ns2 prefix because it assumed the default "" as ns1, therefore "". and this fixes my problem. Until I find a better solution – Andre Brito Nov 30 '21 at 16:07
0

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#">
oraclesoon
  • 799
  • 8
  • 12