Небольшая головоломка JAXB: как определить пользовательский элемент для сериализации объектов Date с информацией TimeZone? Кусок пирога, не так ли? Попробуйте сами, и вы будете удивлены хитрыми деталями.
Мой друг дал мне вызов JAXB на этой неделе: его компания уже использует настройку типа xsd: date в унаследованном коде — сопоставленную с проприетарным типом вместо стандартного типа Calendar. Теперь они также должны представлять объекты Календаря в своей схеме приложения, поэтому им нужно моделировать объекты даты как пользовательский тип. Моя первая мысль была о пятиминутном хаке, просто определив элемент на основе xsd: date и используя настройку JAXB, чтобы сопоставить новый тип с типом Java Calendar. Через пять минут у меня появилось несколько проблем:
-
Настройка календаря по умолчанию в JAXB не сериализует информацию о времени даты. Хорошо, давайте создадим пользовательский класс связывателя и займемся тем, как хотим писать и читать наши данные.
-
Если вы используете xsd: dateTime вместо простого xsd: date, адаптер JAXB по умолчанию больше не работает.
-
Другой сюрприз: вы не можете использовать java.text.SimpleDateFormat для сериализации объектов Date, поскольку строковое представление TimeZone, предоставляемое Java, не совместимо со спецификацией XML.
— новый SimpleDateFormat («гггг-ММ-дд’ТХЧ: мм: ссЗ») производит 2009-12-06T15: 59: 34 + 0100 — ожидаемый формат для типа Schema xsd: dateTime: 2009-12-06T15: 59: 34 + 01: 00 У тебя есть разница? Да, глупое пропущенное двоеточие в представлении часового пояса делает вывод SimpleDateFormat несовместимым со спецификацией схемы XSD. Да, невероятно, но вы должны обрабатывать эти детали программно.
Вы можете попробовать сами, но вместо того, чтобы доказать вам детали, я записал свое
решение по . Если вы знаете более элегантное решение, пожалуйста, дайте мне свой отзыв. Запомните исходную проблему: не использовать xsd: dateTime напрямую, поскольку он уже используется другими настройками. Также: ваши настройки должны поддерживать представление даты и времени, включая часовой пояс.
взлому
Ниже вы найдете транскрипцию примера проекта, который я создал, чтобы проиллюстрировать решение, чтобы упростить копирование, а также чтобы вы могли проверить решение в случае, если вы не хотите или не можете скомпилировать и запустить проект. В противном случае просто скачайте полный проект . Чтобы скомпилировать и запустить проект, откройте терминал и введите следующие команды строки в папке, в которую вы распаковали проект:
mvn clean compile test eclipse:eclipse
Пример проекта Maven
-
Первый шаг, чтобы создать проект maven и настроить плагин JAXB в pom.xml. Для создания проекта я использовал архетип J2SE по умолчанию Maven:
mvn archetype:create -DgroupId=cejug.org -DartifactId=jaxb-example mvn compile eclipse:eclipse
-
Затем вы можете импортировать проект в предпочитаемую IDE и настроить плагин JAXB в pom.xml :
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cejug.org</groupId> <artifactId>jaxb-example</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>jaxb-example</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <pluginRepositories> <pluginRepository> <id>maven2-repository.dev.java.net</id> <name>Java.net Maven 2 Repository</name> <url>http://download.java.net/maven/2 </url> </pluginRepository> </pluginRepositories> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <plugin> <!-- <a href="https://jaxb.dev.java.net/jaxb-maven2-plugin/" title="https://jaxb.dev.java.net/jaxb-maven2-plugin/">https://jaxb.dev.java.net/jaxb-maven2-plugin/</a> --> <groupId>org.jvnet.jaxb2.maven2</groupId> <artifactId>maven-jaxb2-plugin</artifactId> <executions> <execution> <goals> <goal>generate</goal> </goals> </execution> </executions> <configuration> <schemaDirectory>${basedir}/src/main/resources/schema</schemaDirectory> <!-- generateDirectory>${basedir}/src/main/java</generateDirectory--> <includeSchemas> <includeSchema>**/*.xsd</includeSchema> </includeSchemas> <strict>true</strict> <verbose>false</verbose> <extension>true</extension> <readOnly>yes</readOnly> <removeOldOutput>true</removeOldOutput> </configuration> </plugin> </plugins> </build> </project>
-
После этого я создал образец схемы /jaxb-example/src/main/resources/schema/sample-binding.xsd :
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3.org/2001/XMLSchema http://www.w3.org/2001/XMLSchema.xsd" targetNamespace="http://cejug.org/sample" xmlns:sample="http://cejug.org/sample" elementFormDefault="qualified" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" jaxb:extensionBindingPrefixes="xjc" jaxb:version="2.1"> <xsd:annotation> <xsd:appinfo> <jaxb:globalBindings> <xjc:serializable uid="-6026937020915831338" /> <jaxb:javaType name="java.util.Date" xmlType="sample:sample.date" parseMethod="org.cejug.binder.XSDateTimeCustomBinder.parseDateTime" printMethod="org.cejug.binder.XSDateTimeCustomBinder.printDateTime" /> </jaxb:globalBindings> </xsd:appinfo> </xsd:annotation> <xsd:element name="element" type="sample:element.type" /> <xsd:complexType name="element.type"> <xsd:sequence minOccurs="1"> <xsd:element name="jdate" type="sample:sample.date" /> </xsd:sequence> </xsd:complexType> <xsd:simpleType name="sample.date"> <xsd:restriction base="xsd:dateTime" /> </xsd:simpleType> </xsd:schema>
-
Вдохновленный этим блогом, я создал пользовательское связующее org.cejug.binder.XSDateTimeCustomBinder :
package org.cejug.binder; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class XSDateTimeCustomBinder { public static Date parseDateTime(String s) { DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); try { return formatter.parse(s); } catch (ParseException e) { return null; } } // crazy hack because the 'Z' formatter produces an output incompatible with the xsd:dateTime public static String printDateTime(Date dt) { DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); DateFormat tzFormatter = new SimpleDateFormat("Z"); String timezone = tzFormatter.format(dt); return formatter.format(dt) + timezone.substring(0, 3) + ":" + timezone.substring(3); } }
- Затем я создал класс JUnit с помощью следующего метода тестирования:
package cejug.org; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import org.cejug.sample.ElementType; import org.cejug.sample.ObjectFactory; import org.xml.sax.SAXException; public class JaxbSampleTest extends TestCase { private static final String UTF_8 = "UTF-8"; private static final File TEST_FILE = new File("target/test.xml"); public JaxbSampleTest(String testName) { super(testName); } public static Test suite() { return new TestSuite(JaxbSampleTest.class); } @Override protected void setUp() throws Exception { super.setUp(); if (TEST_FILE.exists()) { if (!TEST_FILE.delete()) { fail("impossible to delete the test file, please release it and run the test again"); } } } public void testApp() { ObjectFactory xmlFactory = new ObjectFactory(); ElementType type = new ElementType(); Date calendar = GregorianCalendar.getInstance(TimeZone.getDefault()) .getTime(); type.setJdate(calendar); JAXBElement<ElementType> element = xmlFactory.createElement(type); try { writeXml(element, TEST_FILE); JAXBElement<ElementType> result = read(TEST_FILE); assertEquals(calendar.toString(), result.getValue().getJdate().toString()); } catch (Exception e) { fail(e.getMessage()); } } private void writeXml(JAXBElement<ElementType> sample, File file) throws JAXBException, IOException { FileWriter writer = new FileWriter(file); try { JAXBContext jc = JAXBContext.newInstance(ElementType.class .getPackage().getName(), Thread.currentThread() .getContextClassLoader()); Marshaller m = jc.createMarshaller(); m.setProperty(Marshaller.JAXB_ENCODING, UTF_8); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); m.marshal(sample, writer); } finally { writer.close(); } } @SuppressWarnings("unchecked") public JAXBElement<ElementType> read(File file) throws JAXBException, SAXException, IOException { InputStreamReader reader = new InputStreamReader(new FileInputStream( file)); try { JAXBContext jc = JAXBContext.newInstance(ElementType.class .getPackage().getName(), Thread.currentThread() .getContextClassLoader()); Unmarshaller unmarshaller = jc.createUnmarshaller(); SchemaFactory sf = SchemaFactory .newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema = sf.newSchema(Thread.currentThread() .getContextClassLoader().getResource( "../classes/schema/sample-binding.xsd")); unmarshaller.setSchema(schema); JAXBElement<ElementType> element = (JAXBElement<ElementType>) unmarshaller .unmarshal(reader); return element; } finally { reader.close(); } } }
Вот и все, я надеюсь, это может спасти ваши следующие пять минут взлома ?