Управление префиксами пространств имен в JAXB

Как jaxb определяет список объявлений префиксов пространства имен, которые упорядочивают объект? Я использовал xjc для компиляции классов Java для ebics (схема ebics) . Когда я создаю экземпляр для 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>

Я использовал пользовательский NamespacePrefixMapper для объявления пространства имен и префиксов по умолчанию для ds и xsi. Для пространства имен ds это работает нормально. Но для пространства имен по умолчанию это не так. Он объявляется два раза, один раз как ns2 и один раз как "" последний из моего пользовательского NamespacePrefixMapper.getPreDeclaredNamespaceUris. Я много играл с этим классом. Также я пытался использовать package-info.java, но мне не удалось заставить jaxb использовать "http://www.ebics.org/H003" в качестве пространства имен по умолчанию. Чего я тоже не понимаю, так это появления ns4 и ns5, которые вообще не являются частью xml-документа.

Мой класс 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();
    }


}

я использую


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

person Wilberforce    schedule 02.02.2011    source источник
comment
См. stackoverflow.com/questions/1982977/   -  person skaffman    schedule 02.02.2011
comment
Не могли бы вы прикрепить свою реализацию NamespacePrefixMapper?   -  person sfussenegger    schedule 02.02.2011
comment
‹пред›‹код›‹/код›‹/пре›   -  person Wilberforce    schedule 02.02.2011
comment
Привет, skaffman, проблема с использованием XMLStreamWriter, как это предлагается в ответе на приведенный выше вопрос, заключается в следующем: я хочу маршалировать в org.w3c.dom.Document. Мне не удалось добавить правильную подпись к экземпляру jaxb моего документа, упорядочив фрагмент документа и подписав его. Упорядоченный фрагмент отличается от фрагмента всего упорядоченного документа (опять же из-за объявлений пространств имен), поэтому подпись недействительна при упорядочивании всего документа. Поэтому я пошел на сортировку в Dom Document, затем добавил подпись и затем сериализовал. :-(   -  person Wilberforce    schedule 02.02.2011


Ответы (5)


JAXB всегда добавляет все пространства имен, которые известны JAXBContext, в корневой элемент XML-документа из соображений производительности. См. этот комментарий Kohsuke на JAXB-103 для получения дополнительной информации. .

Единственный способ справиться с этим, который я нашел, - это самостоятельно просмотреть документ после того, как он был создан с помощью 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);
        }
    }

}
person Reboot    schedule 31.01.2012
comment
Как вы проходите документ во второй раз? Использование манипуляций со строками? - person MRalwasser; 02.04.2014
comment
Нет, это делается до сериализации документа путем перебора всех дочерних узлов для каждого элемента в дереве DOM, начиная с корневого узла. - person Reboot; 02.04.2014
comment
Я добавил SOAPHandler‹SOAPMessageContext› в свою службу и вызвал эту утилиту в методе handleMessage(контекст SOAPMessageContext), и она сработала как шарм. Теперь мой веб-сервис CXF удаляет неиспользуемые пространства имен, и служба .NET, к которой он подключается, не жалуется, потому что корневой элемент сообщения SOAP больше разрешенного размера для объявления элемента, который составляет всего 4000 байт! Ура, перезагрузка. - person Craig; 30.10.2014
comment
Есть ли опыт воздействия / производительности этого действия по очистке? - person edbras; 01.01.2016
comment
@edbras Я провел свои личные тесты, используя JMH. Я получил в среднем 78 операций в секунду без добавления и и 74 операции в секунду с добавлением RemoveUnusedNamespaces. Я удалял около 30 неиспользуемых пространств имен в небольшом XML-документе. - person rjdkolb; 21.04.2016

EclipseLink JAXB (MOXy) использует префиксы, указанные в аннотации @XmlSchema (I' m ведущий MOXy). Посмотрите мой ответ на аналогичный вопрос для примера:

person bdoughan    schedule 02.02.2011
comment
Привет Блейз, спасибо за ответ. Я попробовал это решение с моей реализацией jaxb, см. ниже. Мой код работает в среде JEE, и я хотел бы использовать доступную библиотеку jaxb. Я могу проверить MOXy, если не найду решения. @javax.xml.bind.annotation.XmlSchema(namespace = ebics.org/H003, xmlns = { @javax.xml.bind.annotation.XmlNs (префикс = , namespaceURI = ebics.org/H003) , @javax.xml.bind.annotation.XmlNs(prefix = ds, namespaceURI = w3.org /2000/09/xmldsig#) }, elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) - person Wilberforce; 02.02.2011
comment
@Wilberforce - почему-то я пропустил этот комментарий. Если вы используете эталонную реализацию JAXB, вы также можете использовать NamespacePrefixMappiner: jaxb.java.net/ руководство/Changing_prefixes.html - person bdoughan; 04.03.2011
comment
Да, я использовал NamespacePrefixMappiner (см. код выше). Теперь я узнал, что моя проблема была связана с xjc, генерирующим код Java, который ошибочно сгенерировал аннотации с пространством имен атрибутов, установленным на . Это заставило jaxb использовать пространство имен по умолчанию, игнорируя любые пользовательские настройки. Я не выяснил, почему именно так произошло, но после изменения кода вручную все работает нормально. - person Wilberforce; 14.03.2011
comment
@Wilberforce: Вы должны были сделать это в качестве ответа на свой вопрос, чтобы мы могли проголосовать за него. (Самостоятельный ответ приветствуется, если это ответ.) - person Donal Fellows; 28.09.2011

Я нашел способ заставить JAXB удалить префикс ns2, включив следующий атрибут в элемент xs:schema: elementFormDefault="qualified". Таким образом, это будет выглядеть примерно так:

<xs:schema targetNamespace="urn:blah:blah" xmlns="urn:blah:blah" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
person MeplatMasher    schedule 27.07.2012

Я создал свои классы JAXB, используя xjc, но используемая мной веб-служба SOAP заставляет меня следовать некоторым правилам, например не использовать префикс пространства имен.

Это неверно:

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

Это действительно:

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

Как уже отмечалось, JAXB поместил объявление пространства имен в корневой элемент.

Чтобы преодолеть это, первый подход, который я использую, — избегать ненужных элементов в контексте.

Например, установив контекст маршаллера следующим образом:

JAXBContext.newInstance("path.to.package");

Может привести к тому, что JAXB сделает некоторые ненужные объявления пространств имен.

Иногда я могу избавиться от раздражающего xmlns="http://www.w3.org/2000/09/xmldsig#", просто установив контекст с необходимым корневым элементом:

JAXBContext.newInstance(MyRootElement.class);

Второй подход, который я использую, когда первого недостаточно, заключается в том, чтобы весь контекст использовал одно и то же пространство имен. Просто изменив нежелательный "http://www.w3.org/2000/09/xmldsig#", в каждом объявлении пространства имен (например, @XmlElement или @XSchema) к уникальному разрешенному пространству имен (http://www.portalfiscal.inf.br/nfe)

Затем я просто создаю атрибут у нужного ребенка:

@XmlAttribute(name="xmlns")
String xmlns = "http://www.w3.org/2000/09/xmldsig#";

Теперь у меня есть объявление пространства имен из корня, в правильном элементе, без использования префикса.

person Gauss    schedule 23.03.2017

После сканирования многих сообщений решения, использующие NamespacePrefixMapper, зависят от версии JDK (что может привести к поломке кода в будущем), или манипуляции с деревом XML DOM выглядят сложными.

Мой подход грубой силы состоит в том, чтобы манипулировать самим сгенерированным 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));
}

Вывод показывает, что неиспользуемое пространство имен 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#">
person oraclesoon    schedule 17.02.2017