JAXB SAXParseException при отмене сортировки документа с относительным путем к DTD
У меня есть класс, который unmarshals xml из стороннего источника (я не имею никакого контроля над содержимым). Вот фрагмент, который немаршал:
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 ответа
При демаршаллинге из 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: Почему классу вообще нужен файл dtd?
DTD ищет не реализация JAXB, а основной анализатор.
Вопрос № 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);
Вот еще один вариант ответов, которые уже были даны с использованием 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);
}
}