Как распаковать ответ SOAP с помощью JAXB, если объявление пространства имен находится в конверте SOAP?

JAXB может распаковать XML только в том случае, если мыльный конверт уже удален. Однако ответ SOAP, который я пытаюсь разобрать, имеет объявление пространства имен на мыльном конверте. Если я удалю мыльный конверт, объявление пространства имен также будет удалено. Таким образом, префикс тегов не будет ссылаться на none. Это приводит к тому, что JAXB выдает ошибку. Как я могу распаковать ответ SOAP с помощью JAXB, если пространство имен объявлено в конверте SOAP?

Аналогичный образец XML, который мне нужно разобрать, приведен ниже:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://example.com/">
    <soapenv:Body>
        <ns:getNumberResponse>
            <number>123456789</number>
        </ns:getNumberResponse>
    </soapenv:Body>
</soapenv:Envelope>

Вот что произойдет, если я уберу мыльный конверт:

<ns:getNumberResponse>
    <number>123456789</number>
</ns:getNumberResponse>

Если я удалю мыльный конверт, объявление пространства имен также исчезнет. JAXB не сможет распаковать приведенный выше xml, потому что отсутствует определение пространства имен для префикса «ns». Таким образом, он вернет сообщение об ошибке "The prefix "ns" for element "ns:getNumberResponse" is not bound.". Как это исправить?

Обратите внимание, что я использую JDK 1.5. Я не уверен, есть ли способ решить эту проблему в JDK 1.6. Я смог использовать JAXB, потому что загрузил необходимые файлы jar для использования JAXB.


person Arci    schedule 13.07.2012    source источник


Ответы (2)


Вы можете использовать синтаксический анализатор StAX для анализа сообщения SOAP, а затем перейти от XMLStreamReader к элементу getNumberResponse и использовать метод демаршалирования, который принимает параметр класса. Анализатор StAX включен в Java SE 6, но вам потребуется загрузить его для Java SE 5. Woodstox — популярный анализатор StAX.

Ответ

package forum11465653;

public class Response {

    private long number;

    public long getNumber() {
        return number;
    }

    public void setNumber(long number) {
        this.number = number;
    }

}

Демо

У XMLStreamReader есть NamespaceContext. NamespaceContext знает все объявления активных пространств имен для текущего уровня. Ваша реализация JAXB (JSR-222) сможет использовать это для получения необходимой информации.

package forum11465653;

import java.io.FileReader;
import javax.xml.bind.*;
import javax.xml.stream.*;

public class Demo {

    public static void main(String[] args) throws Exception{
        XMLInputFactory xif = XMLInputFactory.newFactory();
        XMLStreamReader xsr = xif.createXMLStreamReader(new FileReader("src/forum11465653/input.xml"));
        xsr.nextTag(); // Advance to Envelope tag
        xsr.nextTag(); // Advance to Body tag
        xsr.nextTag(); // Advance to getNumberResponse tag
        System.out.println(xsr.getNamespaceContext().getNamespaceURI("ns"));

        JAXBContext jc = JAXBContext.newInstance(Response.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        JAXBElement<Response> je = unmarshaller.unmarshal(xsr, Response.class);
        System.out.println(je.getName());
        System.out.println(je.getValue());
    }

}

Вывод

http://example.com/
{http://example.com/}getNumberResponse
forum11465653.Response@781f6226
person bdoughan    schedule 13.07.2012
comment
Привет Блейз! Спасибо! Я видел ваше руководство на blog.bdoughan.com/2010/12/case. -insensitive-unmarshalling.html и попытался немного изменить ваш код, чтобы заменить префикс вместо атрибутов, но я получаю сообщение об ошибке Message: could not determine namespace bound to element prefix ns. Метод, который я переопределяю, это getAttributePrefix(int index). - person Arci; 13.07.2012
comment
@Arci - вам не нужно ничего переопределять. Поскольку вы анализируете весь документ, он будет содержать всю необходимую информацию о пространстве имен. - person bdoughan; 13.07.2012
comment
Прости. Я там запутался. Я думал, что StreamReaderDelegate похож на перехватчик, который вызывается до того, как XML будет демаршаллирован? Основываясь на вашем примере, вы изменили имя локального атрибута и имя атрибута на нижний регистр, чтобы они всегда соответствовали друг другу, когда вы его распаковываете. Вот я и подумал, что должен сделать то же самое с префиксом? Я не смогу распаковать его, если префикс пространства имен будет найден в xml без соответствующего объявления пространства имен, поэтому я планирую полностью удалить все префиксы. Не могли бы вы просветить меня больше по этому вопросу? - person Arci; 13.07.2012
comment
Ура! Спасибо! Теперь это работает! Кстати, я изменил ваш код с XMLInputFactory.newFactory() на XMLInputFactory.newInstance(). - person Arci; 13.07.2012
comment
@Arci - XMLInputFactory.newFactory() теперь является предпочтительным API по сравнению с XMLInputFactory.newInstance(), см.:docs.oracle.com/javase/6/docs/api/javax/xml/stream/. - person bdoughan; 13.07.2012
comment
@Blaise Doughan: Извините. Я использую JDK 1.5. Может быть, поэтому newFactory() не появляется на XMLInputFactory. Спасибо за заметку! Кстати, как я могу получить имя атрибута xsr? Есть что-то вроде xsr.getCurrentTagAttributeName()? Я думаю перебрать xsr, потому что некоторые ответы содержат мыльный конверт. А затем я проверю, нахожусь ли я уже в правильном теге, сравнивая текущее имя атрибута с ожидаемым именем атрибута. - person Arci; 13.07.2012
comment
Сотрите мой последний вопрос. Я уже нашел метод. Я вызываю неправильный метод ранее. Еще раз спасибо! - person Arci; 13.07.2012

Вы можете проанализировать все сообщение SOAP с помощью анализатора DOM, который сможет разрешить все пространства имен во время анализа. Затем извлеките нужный элемент из результирующего дерева DOM и передайте его демаршаллеру.

Демонстрация дома

package forum11465653;

import java.io.File;
import javax.xml.bind.*;
import javax.xml.parsers.*;
import javax.xml.transform.dom.DOMSource;
import org.w3c.dom.*;

public class DomDemo {

    public static void main(String[] args) throws Exception{
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document d = db.parse(new File("src/forum11465653/input.xml"));
        Node getNumberResponseElt = d.getElementsByTagNameNS("http://example.com/", "getNumberResponse").item(0);

        JAXBContext jc = JAXBContext.newInstance(Response.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        JAXBElement<Response> je = unmarshaller.unmarshal(new DOMSource(getNumberResponseElt), Response.class);
        System.out.println(je.getName());
        System.out.println(je.getValue());
    }

}

Вывод

{http://example.com/}getNumberResponse
forum11465653.Response@19632847
person Ian Roberts    schedule 13.07.2012
comment
Спасибо Ян! Я думаю, что это будет работать только в том случае, если только первый элемент тега использует префикс пространства имен. Если будет много внутренних тегов с префиксом пространства имен, тогда также потребуется использовать getElementsByTagName для каждого из этих тегов. Это победит цель unmarshaller. - person Arci; 13.07.2012
comment
Префиксы пространств имен в XML — это синтаксический прием, позволяющий связать правильный URI пространства имен с нужным элементом. Единственное, что имеет значение для нижестоящих компонентов, таких как демаршаллер JAXB, — это URI пространства имен и локальное имя (бит после двоеточия) — когда у вас есть дерево DOM, все элементы на каждом уровне имеют полностью разрешенные пространства имен, а сопоставления префиксов больше не актуально. - person Ian Roberts; 13.07.2012
comment
Ага. Я знаю это. Тем не менее, JAXB не может отменить сортировку, если мыльный конверт все еще присутствует. Проблема в том, что пространство имен объявлено в теге мыльного конверта. Если я удалю конверт, определение пространства имен также исчезнет. JAXB может функционировать должным образом только в том случае, если присутствует соответствующее определение пространства имен. - person Arci; 13.07.2012
comment
По сути, Блейз и я оба предлагаем одно и то же решение, но с использованием разных API-интерфейсов XML — проанализируйте полный документ конверта с помощью инструмента синтаксического анализа XML, который имеет всю доступную информацию о пространстве имен, но передайте только интересующую вас часть XML-дерева. в демаршаллер JAXB. - person Ian Roberts; 13.07.2012
comment
Сопоставления префиксов пространств имен имеют значение только для исходного синтаксического анализатора (DOM, StAX и т. д.). Вы анализируете весь конверт и в итоге получаете древовидную структуру, в которой корневой элемент — это элемент Envelope в пространстве имен http://schemas.xmlsoap.org/soap/envelope/, содержащий элемент Body в пространстве имен http://schemas.xmlsoap.org/soap/envelope/, содержащий элемент getNumberResponse в пространстве имен http://example.com/. Это вся информация, которая нужна демаршаллеру, ему все равно, какие префиксы использовались изначально. Вы передаете JAXB только элемент getNumberResponse. - person Ian Roberts; 13.07.2012
comment
Еще раз спасибо за ваш ответ! Однако, если у getNumberResponse есть префикс, и вы распаковываете его с помощью JAXB, JAXB выдаст исключение, если не сможет найти определение пространства имен префикса. - person Arci; 13.07.2012
comment
Рискуя звучать как застрявшая пластинка, нет, не будет. Если он не сортируется из DOM Node, то ему все равно, какие были префиксы. Сопоставления префиксов имеют значение только в том случае, если они удаляются непосредственно из исходного XML. В любом случае, похоже, что у вас все работает с использованием StAX. - person Ian Roberts; 13.07.2012
comment
+1 - Это определенно сработает. Я расширил ваш демонстрационный код до полного рабочего примера. Я по-прежнему предпочитаю подход StAX, который позволяет избежать необходимости загружать весь документ в память перед его передачей в JAXB: /383861 - person bdoughan; 13.07.2012
comment
@BlaiseDoughan - я бы тоже, если бы подумал об этом :-) - person Ian Roberts; 13.07.2012
comment
@IanRoberts: Да, я уже заработал, используя код Блейза. Спасибо Ян и Блейз! - person Arci; 18.07.2012