JAXB SAXParseException при отмене сортировки документа с относительным путем к DTD - PullRequest
8 голосов
/ 27 августа 2010

У меня есть класс, который unmarshals xml из стороннего источника (я не имею никакого контроля над содержимым).Вот фрагмент, который unmarshals:

JAXBContext jContext = JAXBContext.newInstance("com.optimumlightpath.it.aspenoss.xsd"); 
Unmarshaller unmarshaller = jContext.createUnmarshaller() ;
StringReader xmlStr = new StringReader(str.value);
Connections conns = (Connections) unmarshaller.unmarshal(xmlStr); 

Connections - это класс, созданный dtd-> xsd-> class с использованием xjc.Пакет com.optimumlightpath.it.aspenoss.xsd содержит все такие классы.

Получаемый мною xml содержит относительный путь в DOCTYPE.В основном str.value выше содержит:

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<!DOCTYPE Connections SYSTEM "./dtd/Connections.dtd">
<Connections>
...
</Connections>

Это успешно работает как приложение Java 1.5.Чтобы избежать вышеуказанной ошибки, мне нужно было создать каталог ./dtd вне корня проекта и включить все файлы dtd (не знаю, почему я должен был это сделать, но мы вернемся к этому).

С тех пор я создал веб-сервис на Tomcat5.5, который использует вышеуказанный класс.Я получаю [org.xml.sax.SAXParseException: Relative URI "./dtd/Connections.dtd"; can not be resolved without a document URI.] на линии маршала.Я пытался создать ./dtd в каждой соответствующей папке (корень проекта, WebContent, WEB-INF, рабочий каталог tomcat и т. Д.), Но безрезультатно.

Вопрос № 1: Где я могу найти ./dtd, чтобы класс мог найти его при запуске в качестве веб-службы tomcat?Есть ли какая-либо конфигурация tomcat или службы, которую мне нужно сделать, чтобы распознать каталог?

Вопрос # 2: Почему классу вообще нужен файл dtd?Разве у него нет всей необходимой информации для аннотации в аннотациях класса dtd-> xsd->?Я читал много постов об отключении проверки, настройке EntityResource и других решениях, но этот класс не всегда развертывается как веб-сервис, и я не хочу иметь две последовательности кода.

Ответы [ 3 ]

8 голосов
/ 29 августа 2010

При демаршаллинге из InputStream или Reader анализатор не знает системный идентификатор (uri / location) документа, поэтому он не может разрешить относительные пути.Кажется, парсер пытается разрешить ссылки, используя текущий рабочий каталог, который работает только при запуске из ide или командной строки.Чтобы переопределить это поведение и выполнить разрешение самостоятельно, вам нужно реализовать EntityResolver, как упомянул Блейз Дафан.

После некоторых экспериментов я нашел стандартный способ сделать это.Вам нужно разархивировать от SAXSource, который в свою очередь состоит из XMLReader и InputSource.В этом примере dtd находится рядом с аннотированным классом, поэтому его можно найти в пути к классам.

Main.java

public class Main {
    private static final String FEATURE_NAMESPACES = "http://xml.org/sax/features/namespaces";
    private static final String FEATURE_NAMESPACE_PREFIXES = "http://xml.org/sax/features/namespace-prefixes";

    public static void main(String[] args) throws JAXBException, IOException, SAXException {
        JAXBContext ctx = JAXBContext.newInstance(Root.class);
        Unmarshaller unmarshaller = ctx.createUnmarshaller();

        XMLReader xmlreader = XMLReaderFactory.createXMLReader();
        xmlreader.setFeature(FEATURE_NAMESPACES, true);
        xmlreader.setFeature(FEATURE_NAMESPACE_PREFIXES, true);
        xmlreader.setEntityResolver(new EntityResolver() {
            public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
                // TODO: Check if systemId really references root.dtd
                return new InputSource(Root.class.getResourceAsStream("root.dtd"));
            }
        });

        String xml = "<!DOCTYPE root SYSTEM './root.dtd'><root><element>test</element></root>";
        InputSource input = new InputSource(new StringReader(xml));
        Source source = new SAXSource(xmlreader, input);

        Root root = (Root)unmarshaller.unmarshal(source);
        System.out.println(root.getElement());
    }
}

Root.Java

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
    @XmlElement
    private String element;

    public String getElement() {
        return element;
    }

    public void setElement(String element) {
        this.element = element;
    }
}

root.dtd

<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT root (element)>
<!ELEMENT element (#PCDATA)>
2 голосов
/ 27 августа 2010

Вопрос № 2: Почему класс даже нужен файл dtd?

Это не реализация JAXB, которая ищет DTD, это основной синтаксический анализатор.

Вопрос № 1: Где я могу найти ./dtd чтобы класс мог найти его при запуске как веб-сервис tomcat?

Я не уверен, но ниже я продемонстрирую, как вы можете выполнить эту работу, используя реализацию MOXy JAXB (я технический руководитель), которая будет работать в нескольких средах.

Предлагаемое решение

Создать EntityResolver, который загружает DTD из пути к классам. Таким образом вы можете упаковать DTD в свое приложение и всегда будете знать, где оно находится, независимо от среды развертывания.

public class DtdEntityResolver implements EntityResolver {

    public InputSource resolveEntity(String publicId, String systemId)
            throws SAXException, IOException {
        InputStream dtd = getClass().getClassLoader().getResourceAsStream("dtd/Connections.dtd");
        return new InputSource(dtd);
    }

}

Затем, используя реализацию MOXy JAXB, вы можете перейти к базовой реализации и установить EntityResolver.

import org.eclipse.persistence.jaxb.JAXBHelper;
...
JAXBContext jContext = JAXBContext.newInstance("com.optimumlightpath.it.aspenoss.xsd");
Unmarshaller unmarshaller = jContext.createUnmarshaller() ;
JAXBHelper.getUnmarshaller(unmarshaller).getXMLUnmarshaller().setEntityResolver(new DtdEntityResolver());
StringReader xmlStr = new StringReader(str.value);
Connections conns =(Connections) unmarshaller.unmarshal(xmlStr);
1 голос
/ 31 января 2014

Вот еще один вариант ответов, которые уже были даны с использованием интерфейса EntityResolver.Моя ситуация заключалась в разрешении относительных внешних сущностей XML из одного файла XML в другой в иерархии папок.Параметром для конструктора ниже является «рабочая» папка XML, а не рабочая директория процесса.

public class FileEntityResolver implements EntityResolver {
    private static final URI USER_DIR = SystemUtils.getUserDir().toURI();

    private URI root;

    public FileEntityResolver(File root) {
        this.root = root.toURI();
    }

    @Override @SuppressWarnings("resource")
    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
        URI systemURI;
        try {
            systemURI = new URI(systemId);
        } catch (URISyntaxException e) {
            return null;
        }

        URI relative = USER_DIR.relativize(systemURI);
        URI resolved = root.resolve(relative);

        File f = new File(resolved);
        FileReader fr = new FileReader(f);
        // SAX will close the file reader for us
        return new InputSource(fr);
    }
}
...