Merge branch 'release/5.44.0'
[cdmlib.git] / cdmlib-io / src / main / java / eu / etaxonomy / cdm / io / jaxb / CdmDocumentBuilder.java
1 /**
2 * Copyright (C) 2008 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9
10 package eu.etaxonomy.cdm.io.jaxb;
11
12 import java.io.File;
13 import java.io.FileInputStream;
14 import java.io.FileNotFoundException;
15 import java.io.IOException;
16 import java.io.InputStreamReader;
17 import java.io.Reader;
18 import java.io.UnsupportedEncodingException;
19 import java.io.Writer;
20 import java.net.URISyntaxException;
21
22 import javax.xml.bind.JAXBException;
23 import javax.xml.bind.Marshaller;
24 import javax.xml.bind.PropertyException;
25 import javax.xml.parsers.ParserConfigurationException;
26 import javax.xml.parsers.SAXParser;
27 import javax.xml.parsers.SAXParserFactory;
28 import javax.xml.transform.Source;
29 import javax.xml.transform.sax.SAXResult;
30 import javax.xml.transform.sax.SAXSource;
31 import javax.xml.transform.stream.StreamResult;
32 import javax.xml.validation.Schema;
33 import javax.xml.validation.SchemaFactory;
34
35 import org.apache.logging.log4j.LogManager;
36 import org.apache.logging.log4j.Logger;
37 import org.apache.xml.resolver.tools.CatalogResolver;
38 import org.springframework.core.io.ClassPathResource;
39 import org.springframework.core.io.Resource;
40 import org.springframework.oxm.UncategorizedMappingException;
41 import org.springframework.oxm.XmlMappingException;
42 import org.springframework.oxm.jaxb.Jaxb2Marshaller;
43 import org.xml.sax.InputSource;
44 import org.xml.sax.SAXException;
45 import org.xml.sax.XMLReader;
46 import org.xml.sax.helpers.XMLReaderFactory;
47
48 import eu.etaxonomy.cdm.common.URI;
49 import eu.etaxonomy.cdm.jaxb.CdmNamespacePrefixMapper;
50 import eu.etaxonomy.cdm.jaxb.FormattedText;
51 import eu.etaxonomy.cdm.jaxb.MultilanguageTextElement;
52
53 /**
54 * Initializes a JaxbContext with one class (eu.etaxonomy.cdm.model.DataSet).
55 *
56 * @author a.babadshanjan, ben.clark
57 */
58 //Binds it to XML schemas found in /src/main/resources/schema/cdm (cdm.xsd, common.xsd, name.xsd).
59 //There is a bit of magic with a resource resolver in eu.etaxonomy.cdm.io.jaxb
60 //which allows to package the schemas into a jar file.
61 public class CdmDocumentBuilder extends Jaxb2Marshaller {
62
63 private static final Logger logger = LogManager.getLogger();
64 private boolean formattedOutput = Boolean.TRUE;
65 private String encoding = "UTF-8";
66
67 public static String CDM_NAMESPACE = "eu.etaxonomy.cdm.model";
68 public static String[] CDM_SCHEMA_FILES = { "/schema/cdm/agent.xsd",
69 "/schema/cdm/cdm.xsd",
70 "/schema/cdm/common.xsd",
71 "/schema/cdm/description.xsd",
72 "/schema/cdm/location.xsd",
73 "/schema/cdm/media.xsd",
74 "/schema/cdm/molecular.xsd",
75 "/schema/cdm/name.xsd",
76 "/schema/cdm/occurrence.xsd",
77 "/schema/cdm/reference.xsd",
78 "/schema/cdm/taxon.xsd"};
79 public static Class[] CONTEXT_CLASSES = {
80 DataSet.class,FormattedText.class,MultilanguageTextElement.class};
81
82 private Resource schemas[];
83
84 protected String[] getSchemaFiles() {
85 return CDM_SCHEMA_FILES;
86 }
87
88 protected Class[] getContextClasses() {
89 return CONTEXT_CLASSES;
90 }
91
92 public CdmDocumentBuilder() {
93 schemas = new Resource[CDM_SCHEMA_FILES.length];
94
95 for(int i = 0; i < CDM_SCHEMA_FILES.length; i++) {
96 schemas[i] = new ClassPathResource(CDM_SCHEMA_FILES[i]);
97 }
98
99 super.setSchemas(schemas);
100 super.setClassesToBeBound(CONTEXT_CLASSES);
101 super.setSchemaLanguage("http://www.w3.org/2001/XMLSchema");
102 }
103
104 public CdmDocumentBuilder(boolean formattedOutput, String encoding) {
105 this.formattedOutput = formattedOutput;
106 this.encoding = encoding;
107 }
108
109
110 @Override
111 protected void initJaxbMarshaller(Marshaller marshaller) throws JAXBException {
112 try {
113 marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new CdmNamespacePrefixMapper() );
114
115 // For test purposes insert newlines to make the XML output readable
116 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, formattedOutput);
117 //marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION,"http://etaxonomy.eu/cdm/model/1.0 schema/cdm/cdm.xsd");
118 //marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION,"http://etaxonomy.eu/cdm/model/1.0 cdm.xsd");
119 marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding);
120
121 CdmMarshallerListener marshallerListener = new CdmMarshallerListener();
122 marshaller.setListener(marshallerListener);
123 marshaller.setEventHandler(new WarningTolerantValidationEventHandler());
124 } catch(PropertyException pe) {
125 throw new JAXBException(pe.getMessage(),pe);
126 }
127 }
128
129 protected <T> T unmarshal(Class<T> clazz, InputSource input) {
130 SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
131 Schema schema;
132 try {
133 schema = createSchema();
134 saxParserFactory.setNamespaceAware(true);
135 saxParserFactory.setXIncludeAware(true);
136 saxParserFactory.setValidating(true);
137 saxParserFactory.setSchema(schema);
138
139 SAXParser saxParser = saxParserFactory.newSAXParser();
140 XMLReader xmlReader = saxParser.getXMLReader();
141 xmlReader.setEntityResolver(new CatalogResolver());
142 xmlReader.setErrorHandler(new DefaultErrorHandler());
143 SAXSource saxSource = new SAXSource( xmlReader, input);
144 saxSource.setSystemId(input.getSystemId());
145
146 return (T)super.unmarshal(saxSource);
147 } catch (IOException e) {
148 throw new UncategorizedMappingException(e.getMessage(), e);
149 } catch (SAXException e) {
150 throw new UncategorizedMappingException(e.getMessage(), e);
151 } catch (ParserConfigurationException e) {
152 throw new UncategorizedMappingException(e.getMessage(), e);
153 }
154 }
155
156 private Schema createSchema() throws SAXException, IOException {
157 //method created to avoid dependency of spring-xml like in earlier versions
158 //old implementation was schema = SchemaLoaderUtils.loadSchema(schemas, "http://www.w3.org/2001/XMLSchema");
159 //maybe we can improve this loading in future
160
161 String schemaLanguage = "http://www.w3.org/2001/XMLSchema";
162 Source[] schemaSources = new Source[schemas.length];
163 XMLReader reader = XMLReaderFactory.createXMLReader();
164 reader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
165 for (int i = 0; i < schemas.length; i++) {
166 schemaSources[i] = makeSchemaSource(reader, schemas[i]);// new ResourceSource(reader, schemas[i]);
167 }
168 SchemaFactory schemaFactory = SchemaFactory.newInstance(schemaLanguage);
169 return schemaFactory.newSchema(schemaSources);
170 }
171
172 private Source makeSchemaSource(XMLReader reader, Resource resource) throws IOException {
173 Source result = new SAXSource(reader, createInputSource(resource));
174 return result;
175 }
176
177 private static InputSource createInputSource(Resource resource) throws IOException {
178 InputSource inputSource = new InputSource(resource.getInputStream());
179 inputSource.setSystemId(getSystemId(resource));
180 return inputSource;
181 }
182
183 /** Retrieves the URL from the given resource as System ID. Returns <code>null</code> if it cannot be opened. */
184 private static String getSystemId(Resource resource) {
185 try {
186 return new URI(resource.getURL().toExternalForm()).toString();
187 }
188 catch (IOException ex) {
189 logger.debug("Could not get System ID from [" + resource + "], ex");
190 return null;
191 }
192 catch (URISyntaxException e) {
193 logger.debug("Could not get System ID from [" + resource + "], ex");
194 return null;
195 }
196 }
197
198 public <T> T unmarshal(Class<T> clazz,Reader reader) throws XmlMappingException {
199 InputSource source = new InputSource(reader);
200 return unmarshal(clazz,source);
201 }
202
203 public <T> T unmarshal(Class<T> clazz,Reader reader, String systemId) throws XmlMappingException {
204 InputSource input = new InputSource(reader);
205 input.setSystemId(systemId);
206 return unmarshal(clazz,input);
207 }
208
209 public <T> T unmarshal(Class<T> clazz, File file) throws XmlMappingException {
210
211 InputSource input;
212 try {
213 input = new InputSource(new InputStreamReader(new FileInputStream(file),encoding));
214 return unmarshal(clazz,input);
215 } catch (UnsupportedEncodingException e) {
216 throw new UncategorizedMappingException(e.getMessage(), e);
217 } catch (FileNotFoundException e) {
218 throw new UncategorizedMappingException(e.getMessage(), e);
219 }
220 }
221
222 public void marshal(DataSet dataSet, Writer writer) throws XmlMappingException {
223 logger.info("Start marshalling");
224 super.marshal(dataSet, new StreamResult(writer));
225 }
226
227 public void marshal(DataSet dataSet, StreamResult result) throws XmlMappingException {
228 logger.info("Start marshalling");
229 super.marshal(dataSet, result);
230 }
231
232 public void marshal(DataSet dataSet, SAXResult result) throws XmlMappingException {
233 logger.info("Start marshalling");
234 super.marshal(dataSet, result);
235 }
236 }