Controlling namespace prefixes in JAXB

前端 未结 5 766
无人及你
无人及你 2020-12-03 12:05

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

相关标签:
5条回答
  • 2020-12-03 12:44

    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">
    
    0 讨论(0)
  • 2020-12-03 12:48

    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#">
    
    0 讨论(0)
  • 2020-12-03 12:54

    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.

    0 讨论(0)
  • 2020-12-03 13:00

    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);
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-12-03 13:02

    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)
    0 讨论(0)
提交回复
热议问题