Controlling namespace prefixes in JAXB

倖福魔咒の 提交于 2019-11-27 14:29:07
Reboot

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

}
bdoughan

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:

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.

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#">
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!