简体   繁体   English

在JAXB中控制名称空间前缀

[英]Controlling namespace prefixes in JAXB

How does jaxb determine the list of namespace prefix declarations whem marshalling an object? jaxb如何确定编组对象的命名空间前缀声明列表? I used xjc to compile java classes for ebics ( ebics schema ). 我使用xjc为ebics( ebics schema )编译java类。 When I create an instance for an ebicsRequest it looks like this: 当我为ebicsRequest创建一个实例时,它看起来像这样:


<?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. 我使用自定义NamespacePrefixMapper来声明ds和xsi的默认命名空间和前缀。 For the namespace ds it works fine. 对于命名空间ds,它可以正常工作。 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 . 它被声明两次一次为ns2,一次为“”后者来自我的自定义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. 此外,我尝试使用package-info.java但我无法使jaxb使用"http://www.ebics.org/H003"作为默认命名空间。 What I also do not understand is the appearance of ns4 and ns5 which are not at all part of the xml document. 我还不明白的是ns4和ns5的外观,它们不是xml文档的全部内容。

My NamespacePrefixMapper class looks like 我的NamespacePrefixMapper类看起来像


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. 出于性能原因,JAXB始终将JAXBContext已知的所有命名空间添加到XML文档的根元素。 See this comment by Kohsuke on JAXB-103 for more information. 有关更多信息,请参阅Kohsuke在JAXB-103上发表的评论。

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: 我发现处理此问题的唯一方法是在使用JAXB创建文档后自行遍历文档,并使用以下帮助程序类删除所有未使用的名称空间:

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

}

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. 我使用xjc生成了我的JAXB类,但是我使用的SOAP WebService强制我遵循一些规则,比如不使用命名空间前缀。

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. 正如所指出的,JAXB将名称空间声明放在根元素上。

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. 可以引导JAXB对命名空间进行一些不必要的声明。

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: 有时候,我可以通过烦人的xmlns =“http://www.w3.org/2000/09/xmldsig#”来设置上下文,并使用必要的根元素:

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 ) 只需将每个名称空间声明(如@XmlElement或@XSchema)中不需要的“ http://www.w3.org/2000/09/xmldsig# ”更改为允许的唯一名称空间( 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. 现在我在根目录中使用正确的元素,而不使用任何前缀。

EclipseLink JAXB (MOXy) uses the prefixes as specified in the @XmlSchema annotation (I'm the MOXy lead). EclipseLink JAXB(MOXy)使用@XmlSchema注释中指定的前缀(我是MOXy引导)。 Check out my answer to a similar question for an example: 查看我对类似问题的答案,举个例子:

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". 我发现让JAXB删除ns2前缀的方法是在xs:schema元素中包含以下属性: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. 在抓取许多帖子之后,使用NamespacePrefixMapper的解决方案依赖于JDK版本(可能在将来破坏代码)或XML DOM树操作看起来很复杂。

My brute-force approach is to manipulate the generated XML itself. 我的蛮力方法是操纵生成的XML本身。

/**
 * 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: 输出显示未使用的xmlns命名空间被过滤掉:

<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#">

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM