JibxMarshaller.java 15.6 KB
Newer Older
A
Arjen Poutsma 已提交
1
/*
2
 * Copyright 2002-2013 the original author or authors.
A
Arjen Poutsma 已提交
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.oxm.jibx;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
31 32
import javax.xml.transform.Result;
import javax.xml.transform.Source;
A
Arjen Poutsma 已提交
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.jibx.runtime.BindingDirectory;
import org.jibx.runtime.IBindingFactory;
import org.jibx.runtime.IMarshallingContext;
import org.jibx.runtime.IUnmarshallingContext;
import org.jibx.runtime.IXMLReader;
import org.jibx.runtime.IXMLWriter;
import org.jibx.runtime.JiBXException;
50
import org.jibx.runtime.ValidationException;
A
Arjen Poutsma 已提交
51 52 53 54 55 56 57 58 59 60 61
import org.jibx.runtime.impl.MarshallingContext;
import org.jibx.runtime.impl.StAXReaderWrapper;
import org.jibx.runtime.impl.StAXWriter;
import org.jibx.runtime.impl.UnmarshallingContext;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;

import org.springframework.beans.factory.InitializingBean;
62 63 64
import org.springframework.oxm.MarshallingFailureException;
import org.springframework.oxm.UnmarshallingFailureException;
import org.springframework.oxm.ValidationFailureException;
A
Arjen Poutsma 已提交
65
import org.springframework.oxm.XmlMappingException;
66
import org.springframework.oxm.support.AbstractMarshaller;
A
Arjen Poutsma 已提交
67
import org.springframework.util.Assert;
68
import org.springframework.util.ClassUtils;
69
import org.springframework.util.StringUtils;
A
Arjen Poutsma 已提交
70
import org.springframework.util.xml.StaxUtils;
A
Arjen Poutsma 已提交
71 72

/**
73
 * Implementation of the {@code Marshaller} and {@code Unmarshaller} interfaces for JiBX.
74
 *
75 76
 * <p>The typical usage will be to set the {@code targetClass} and optionally the
 * {@code bindingName} property on this bean.
A
Arjen Poutsma 已提交
77 78
 *
 * @author Arjen Poutsma
79
 * @since 3.0
A
Arjen Poutsma 已提交
80 81 82 83 84
 * @see org.jibx.runtime.IMarshallingContext
 * @see org.jibx.runtime.IUnmarshallingContext
 */
public class JibxMarshaller extends AbstractMarshaller implements InitializingBean {

85 86
	private static final String DEFAULT_BINDING_NAME = "binding";

87

88
	private Class<?> targetClass;
A
Arjen Poutsma 已提交
89

90 91
	private String targetPackage;

A
Arjen Poutsma 已提交
92 93 94 95
	private String bindingName;

	private int indent = -1;

A
SWS-572  
Arjen Poutsma 已提交
96
	private String encoding = "UTF-8";
A
Arjen Poutsma 已提交
97 98 99

	private Boolean standalone;

100 101 102 103 104 105 106 107
	private String docTypeRootElementName;

	private String docTypeSystemId;

	private String docTypePublicId;

	private String docTypeInternalSubset;

108 109
	private IBindingFactory bindingFactory;

J
Juergen Hoeller 已提交
110
	private final TransformerFactory transformerFactory = TransformerFactory.newInstance();
111 112


113
	/**
114 115 116
	 * Set the target class for this instance. Setting either this property or the
	 * {@link #setTargetPackage(String) targetPackage} property is required.
	 * <p>If this property is set, {@link #setTargetPackage(String) targetPackage} is ignored.
117
	 */
118
	public void setTargetClass(Class<?> targetClass) {
119
		this.targetClass = targetClass;
A
Arjen Poutsma 已提交
120 121
	}

122 123 124 125 126 127 128 129
	/**
	 * Set the target package for this instance. Setting either this property or the
	 * {@link #setTargetClass(Class) targetClass} property is required.
	 * <p>If {@link #setTargetClass(Class) targetClass} is set, this property is ignored.
	 */
	public void setTargetPackage(String targetPackage) {
		this.targetPackage = targetPackage;
	}
J
Juergen Hoeller 已提交
130

131
	/**
132
	 * Set the optional binding name for this instance.
133
	 */
134 135
	public void setBindingName(String bindingName) {
		this.bindingName = bindingName;
A
Arjen Poutsma 已提交
136 137
	}

138
	/**
139
	 * Set the number of nesting indent spaces. Default is {@code -1}, i.e. no indentation.
140
	 */
A
Arjen Poutsma 已提交
141 142 143 144
	public void setIndent(int indent) {
		this.indent = indent;
	}

145
	/**
146
	 * Set the document encoding using for marshalling. Default is UTF-8.
147
	 */
A
Arjen Poutsma 已提交
148 149 150 151
	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

152
	/**
153
	 * Set the document standalone flag for marshalling. By default, this flag is not present.
154
	 */
A
Arjen Poutsma 已提交
155 156 157 158
	public void setStandalone(Boolean standalone) {
		this.standalone = standalone;
	}

159
	/**
160 161 162
	 * Set the root element name for the DTD declaration written when marshalling.
	 * By default, this is {@code null} (i.e. no DTD declaration is written).
	 * <p>If set to a value, the system ID or public ID also need to be set.
163 164 165 166 167 168 169 170
	 * @see #setDocTypeSystemId(String)
	 * @see #setDocTypePublicId(String)
	 */
	public void setDocTypeRootElementName(String docTypeRootElementName) {
		this.docTypeRootElementName = docTypeRootElementName;
	}

	/**
J
Juergen Hoeller 已提交
171
	 * Set the system id for the DTD declaration written when marshalling.
172 173
	 * By default, this is {@code null}. Only used when the root element also has been set.
	 * <p>Set either this property or {@code docTypePublicId}, not both.
174 175 176 177 178 179 180
	 * @see #setDocTypeRootElementName(String)
	 */
	public void setDocTypeSystemId(String docTypeSystemId) {
		this.docTypeSystemId = docTypeSystemId;
	}

	/**
J
Juergen Hoeller 已提交
181
	 * Set the public id for the DTD declaration written when marshalling.
182 183
	 * By default, this is {@code null}. Only used when the root element also has been set.
	 * <p>Set either this property or {@code docTypeSystemId}, not both.
184 185 186 187 188 189 190
	 * @see #setDocTypeRootElementName(String)
	 */
	public void setDocTypePublicId(String docTypePublicId) {
		this.docTypePublicId = docTypePublicId;
	}

	/**
191 192
	 * Set the internal subset Id for the DTD declaration written when marshalling.
	 * By default, this is {@code null}. Only used when the root element also has been set.
193 194 195 196 197
	 * @see #setDocTypeRootElementName(String)
	 */
	public void setDocTypeInternalSubset(String docTypeInternalSubset) {
		this.docTypeInternalSubset = docTypeInternalSubset;
	}
198

199

200
	@Override
201
	public void afterPropertiesSet() throws JiBXException {
202 203 204 205 206 207 208 209 210 211 212 213 214
		if (this.targetClass != null) {
			if (StringUtils.hasLength(this.bindingName)) {
				if (logger.isInfoEnabled()) {
					logger.info("Configured for target class [" + this.targetClass + "] using binding [" + this.bindingName + "]");
				}
				this.bindingFactory = BindingDirectory.getFactory(this.bindingName, this.targetClass);
			}
			else {
				if (logger.isInfoEnabled()) {
					logger.info("Configured for target class [" + this.targetClass + "]");
				}
				this.bindingFactory = BindingDirectory.getFactory(this.targetClass);
			}
215 216
		}
		else if (this.targetPackage != null) {
217 218
			if (!StringUtils.hasLength(bindingName)) {
				bindingName = DEFAULT_BINDING_NAME;
A
Arjen Poutsma 已提交
219
			}
220
			if (logger.isInfoEnabled()) {
J
Juergen Hoeller 已提交
221
				logger.info("Configured for target package [" + this.targetPackage	+ "] using binding [" + this.bindingName + "]");
A
Arjen Poutsma 已提交
222
			}
J
Juergen Hoeller 已提交
223
			this.bindingFactory = BindingDirectory.getFactory(this.bindingName, this.targetPackage);
224 225
		}
		else {
J
Juergen Hoeller 已提交
226
			throw new IllegalArgumentException("Either 'targetClass' or 'targetPackage' is required");
A
Arjen Poutsma 已提交
227 228 229
		}
	}

230

231
	@Override
232
	public boolean supports(Class<?> clazz) {
A
Arjen Poutsma 已提交
233
		Assert.notNull(clazz, "'clazz' must not be null");
234 235 236
		if (this.targetClass != null) {
			return this.targetClass.equals(clazz);
		}
237
		String[] mappedClasses = this.bindingFactory.getMappedClasses();
A
Arjen Poutsma 已提交
238 239 240 241 242 243 244 245 246 247
		String className = clazz.getName();
		for (String mappedClass : mappedClasses) {
			if (className.equals(mappedClass)) {
				return true;
			}
		}
		return false;
	}


248
	// Supported marshalling
A
Arjen Poutsma 已提交
249 250

	@Override
A
Arjen Poutsma 已提交
251
	protected void marshalOutputStream(Object graph, OutputStream outputStream)
A
Arjen Poutsma 已提交
252 253 254
			throws XmlMappingException, IOException {
		try {
			IMarshallingContext marshallingContext = createMarshallingContext();
255 256
			marshallingContext.startDocument(this.encoding, this.standalone, outputStream);
			marshalDocument(marshallingContext, graph);
A
Arjen Poutsma 已提交
257 258 259 260 261 262 263
		}
		catch (JiBXException ex) {
			throw convertJibxException(ex, true);
		}
	}

	@Override
A
Arjen Poutsma 已提交
264
	protected void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException {
A
Arjen Poutsma 已提交
265 266
		try {
			IMarshallingContext marshallingContext = createMarshallingContext();
267 268
			marshallingContext.startDocument(this.encoding, this.standalone, writer);
			marshalDocument(marshallingContext, graph);
A
Arjen Poutsma 已提交
269 270 271 272 273 274
		}
		catch (JiBXException ex) {
			throw convertJibxException(ex, true);
		}
	}

275
	private void marshalDocument(IMarshallingContext marshallingContext, Object graph) throws IOException, JiBXException {
276 277 278 279 280 281 282
		if (StringUtils.hasLength(docTypeRootElementName)) {
			IXMLWriter xmlWriter = marshallingContext.getXmlWriter();
			xmlWriter.writeDocType(docTypeRootElementName, docTypeSystemId, docTypePublicId, docTypeInternalSubset);
		}
		marshallingContext.marshalDocument(graph);
	}

A
Arjen Poutsma 已提交
283

284
	// Unsupported marshalling
A
Arjen Poutsma 已提交
285 286

	@Override
A
Arjen Poutsma 已提交
287
	protected void marshalDomNode(Object graph, Node node) throws XmlMappingException {
A
Arjen Poutsma 已提交
288 289
		try {
			// JiBX does not support DOM natively, so we write to a buffer first, and transform that to the Node
290 291
			Result result = new DOMResult(node);
			transformAndMarshal(graph, result);
A
Arjen Poutsma 已提交
292
		}
293
		catch (IOException ex) {
294
			throw new MarshallingFailureException("JiBX marshalling exception", ex);
A
Arjen Poutsma 已提交
295 296 297
		}
	}

298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
	@Override
	protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) {
		XMLStreamWriter streamWriter = StaxUtils.createEventStreamWriter(eventWriter);
		marshalXmlStreamWriter(graph, streamWriter);
	}

	@Override
	protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException {
		try {
			MarshallingContext marshallingContext = (MarshallingContext) createMarshallingContext();
			IXMLWriter xmlWriter = new StAXWriter(marshallingContext.getNamespaces(), streamWriter);
			marshallingContext.setXmlWriter(xmlWriter);
			marshallingContext.marshalDocument(graph);
		}
		catch (JiBXException ex) {
			throw convertJibxException(ex, false);
		}
	}

A
Arjen Poutsma 已提交
317
	@Override
A
Arjen Poutsma 已提交
318
	protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler)
A
Arjen Poutsma 已提交
319 320 321
			throws XmlMappingException {
		try {
			// JiBX does not support SAX natively, so we write to a buffer first, and transform that to the handlers
322 323 324 325 326 327 328 329 330 331 332
			SAXResult saxResult = new SAXResult(contentHandler);
			saxResult.setLexicalHandler(lexicalHandler);
			transformAndMarshal(graph, saxResult);
		}
		catch (IOException ex) {
			throw new MarshallingFailureException("JiBX marshalling exception", ex);
		}
	}

	private void transformAndMarshal(Object graph, Result result) throws IOException {
		try {
A
Arjen Poutsma 已提交
333 334 335
			ByteArrayOutputStream os = new ByteArrayOutputStream();
			marshalOutputStream(graph, os);
			ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
336
			Transformer transformer = this.transformerFactory.newTransformer();
337
			transformer.transform(new StreamSource(is), result);
A
Arjen Poutsma 已提交
338
		}
339 340 341
		catch (TransformerException ex) {
			throw new MarshallingFailureException(
					"Could not transform to [" + ClassUtils.getShortName(result.getClass()) + "]");
A
Arjen Poutsma 已提交
342
		}
343

A
Arjen Poutsma 已提交
344 345
	}

346

A
Arjen Poutsma 已提交
347
	// Unmarshalling
A
Arjen Poutsma 已提交
348

A
Arjen Poutsma 已提交
349
	@Override
350
	protected Object unmarshalXmlEventReader(XMLEventReader eventReader) {
A
Arjen Poutsma 已提交
351
		try {
352 353
			XMLStreamReader streamReader = StaxUtils.createEventStreamReader(eventReader);
			return unmarshalXmlStreamReader(streamReader);
A
Arjen Poutsma 已提交
354
		}
355 356
		catch (XMLStreamException ex) {
			return new UnmarshallingFailureException("JiBX unmarshalling exception", ex);
A
Arjen Poutsma 已提交
357 358 359 360
		}
	}

	@Override
361
	protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) {
A
Arjen Poutsma 已提交
362
		try {
363 364 365 366
			UnmarshallingContext unmarshallingContext = (UnmarshallingContext) createUnmarshallingContext();
			IXMLReader xmlReader = new StAXReaderWrapper(streamReader, null, true);
			unmarshallingContext.setDocument(xmlReader);
			return unmarshallingContext.unmarshalElement();
A
Arjen Poutsma 已提交
367 368 369 370 371 372 373
		}
		catch (JiBXException ex) {
			throw convertJibxException(ex, false);
		}
	}

	@Override
374
	protected Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException {
A
Arjen Poutsma 已提交
375
		try {
376 377
			IUnmarshallingContext unmarshallingContext = createUnmarshallingContext();
			return unmarshallingContext.unmarshalDocument(inputStream, encoding);
A
Arjen Poutsma 已提交
378 379 380 381 382 383 384
		}
		catch (JiBXException ex) {
			throw convertJibxException(ex, false);
		}
	}

	@Override
385
	protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOException {
A
Arjen Poutsma 已提交
386
		try {
387 388
			IUnmarshallingContext unmarshallingContext = createUnmarshallingContext();
			return unmarshallingContext.unmarshalDocument(reader);
A
Arjen Poutsma 已提交
389
		}
390 391
		catch (JiBXException ex) {
			throw convertJibxException(ex, false);
A
Arjen Poutsma 已提交
392 393 394
		}
	}

395

A
Arjen Poutsma 已提交
396 397 398
	// Unsupported Unmarshalling

	@Override
A
Arjen Poutsma 已提交
399
	protected Object unmarshalDomNode(Node node) throws XmlMappingException {
A
Arjen Poutsma 已提交
400
		try {
401
			return transformAndUnmarshal(new DOMSource(node));
A
Arjen Poutsma 已提交
402
		}
403
		catch (IOException ex) {
404
			throw new UnmarshallingFailureException("JiBX unmarshalling exception", ex);
A
Arjen Poutsma 已提交
405 406 407 408
		}
	}

	@Override
A
Arjen Poutsma 已提交
409
	protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
A
Arjen Poutsma 已提交
410
			throws XmlMappingException, IOException {
J
Juergen Hoeller 已提交
411

412 413 414 415
		return transformAndUnmarshal(new SAXSource(xmlReader, inputSource));
	}

	private Object transformAndUnmarshal(Source source) throws IOException {
A
Arjen Poutsma 已提交
416
		try {
J
Juergen Hoeller 已提交
417
			Transformer transformer = this.transformerFactory.newTransformer();
A
Arjen Poutsma 已提交
418
			ByteArrayOutputStream os = new ByteArrayOutputStream();
419
			transformer.transform(source, new StreamResult(os));
A
Arjen Poutsma 已提交
420 421 422 423
			ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
			return unmarshalInputStream(is);
		}
		catch (TransformerException ex) {
424 425
			throw new MarshallingFailureException(
					"Could not transform from [" + ClassUtils.getShortName(source.getClass()) + "]");
A
Arjen Poutsma 已提交
426 427 428
		}
	}

429

A
Arjen Poutsma 已提交
430
	/**
431
	 * Create a new {@code IMarshallingContext}, configured with the correct indentation.
A
Arjen Poutsma 已提交
432 433 434 435
	 * @return the created marshalling context
	 * @throws JiBXException in case of errors
	 */
	protected IMarshallingContext createMarshallingContext() throws JiBXException {
436 437
		IMarshallingContext marshallingContext = this.bindingFactory.createMarshallingContext();
		marshallingContext.setIndent(this.indent);
A
Arjen Poutsma 已提交
438 439 440 441
		return marshallingContext;
	}

	/**
442
	 * Create a new {@code IUnmarshallingContext}.
A
Arjen Poutsma 已提交
443 444 445 446
	 * @return the created unmarshalling context
	 * @throws JiBXException in case of errors
	 */
	protected IUnmarshallingContext createUnmarshallingContext() throws JiBXException {
447
		return this.bindingFactory.createUnmarshallingContext();
A
Arjen Poutsma 已提交
448
	}
A
Arjen Poutsma 已提交
449

450
	/**
451 452
	 * Convert the given {@code JiBXException} to an appropriate exception from the
	 * {@code org.springframework.oxm} hierarchy.
453 454
	 * <p>A boolean flag is used to indicate whether this exception occurs during marshalling or
	 * unmarshalling, since JiBX itself does not make this distinction in its exception hierarchy.
455 456 457 458
	 * @param ex {@code JiBXException} that occured
	 * @param marshalling indicates whether the exception occurs during marshalling ({@code true}),
	 * or unmarshalling ({@code false})
	 * @return the corresponding {@code XmlMappingException}
459 460 461 462 463 464 465 466 467 468 469 470 471 472
	 */
	public XmlMappingException convertJibxException(JiBXException ex, boolean marshalling) {
		if (ex instanceof ValidationException) {
			return new ValidationFailureException("JiBX validation exception", ex);
		}
		else {
			if (marshalling) {
				return new MarshallingFailureException("JiBX marshalling exception", ex);
			}
			else {
				return new UnmarshallingFailureException("JiBX unmarshalling exception", ex);
			}
		}
	}
A
Arjen Poutsma 已提交
473 474

}