001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.oxm.jibx; 018 019import java.io.ByteArrayInputStream; 020import java.io.ByteArrayOutputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.OutputStream; 024import java.io.Reader; 025import java.io.Writer; 026import javax.xml.stream.XMLEventReader; 027import javax.xml.stream.XMLEventWriter; 028import javax.xml.stream.XMLStreamException; 029import javax.xml.stream.XMLStreamReader; 030import javax.xml.stream.XMLStreamWriter; 031import javax.xml.transform.OutputKeys; 032import javax.xml.transform.Result; 033import javax.xml.transform.Source; 034import javax.xml.transform.Transformer; 035import javax.xml.transform.TransformerException; 036import javax.xml.transform.TransformerFactory; 037import javax.xml.transform.dom.DOMResult; 038import javax.xml.transform.dom.DOMSource; 039import javax.xml.transform.sax.SAXResult; 040import javax.xml.transform.sax.SAXSource; 041import javax.xml.transform.stream.StreamResult; 042import javax.xml.transform.stream.StreamSource; 043 044import org.jibx.runtime.BindingDirectory; 045import org.jibx.runtime.IBindingFactory; 046import org.jibx.runtime.IMarshallingContext; 047import org.jibx.runtime.IUnmarshallingContext; 048import org.jibx.runtime.IXMLReader; 049import org.jibx.runtime.IXMLWriter; 050import org.jibx.runtime.JiBXException; 051import org.jibx.runtime.ValidationException; 052import org.jibx.runtime.impl.MarshallingContext; 053import org.jibx.runtime.impl.StAXReaderWrapper; 054import org.jibx.runtime.impl.StAXWriter; 055import org.jibx.runtime.impl.UnmarshallingContext; 056import org.w3c.dom.Node; 057import org.xml.sax.ContentHandler; 058import org.xml.sax.InputSource; 059import org.xml.sax.XMLReader; 060import org.xml.sax.ext.LexicalHandler; 061 062import org.springframework.beans.factory.InitializingBean; 063import org.springframework.oxm.MarshallingFailureException; 064import org.springframework.oxm.UnmarshallingFailureException; 065import org.springframework.oxm.ValidationFailureException; 066import org.springframework.oxm.XmlMappingException; 067import org.springframework.oxm.support.AbstractMarshaller; 068import org.springframework.util.Assert; 069import org.springframework.util.ClassUtils; 070import org.springframework.util.StringUtils; 071import org.springframework.util.xml.StaxUtils; 072 073/** 074 * Implementation of the {@code Marshaller} and {@code Unmarshaller} interfaces for JiBX. 075 * 076 * <p>The typical usage will be to set the {@code targetClass} and optionally the 077 * {@code bindingName} property on this bean. 078 * 079 * @author Arjen Poutsma 080 * @since 3.0 081 * @see org.jibx.runtime.IMarshallingContext 082 * @see org.jibx.runtime.IUnmarshallingContext 083 */ 084public class JibxMarshaller extends AbstractMarshaller implements InitializingBean { 085 086 private static final String DEFAULT_BINDING_NAME = "binding"; 087 088 089 private Class<?> targetClass; 090 091 private String targetPackage; 092 093 private String bindingName; 094 095 private int indent = -1; 096 097 private String encoding = "UTF-8"; 098 099 private Boolean standalone; 100 101 private String docTypeRootElementName; 102 103 private String docTypeSystemId; 104 105 private String docTypePublicId; 106 107 private String docTypeInternalSubset; 108 109 private IBindingFactory bindingFactory; 110 111 private final TransformerFactory transformerFactory = TransformerFactory.newInstance(); 112 113 114 /** 115 * Set the target class for this instance. Setting either this property or the 116 * {@link #setTargetPackage(String) targetPackage} property is required. 117 * <p>If this property is set, {@link #setTargetPackage(String) targetPackage} is ignored. 118 */ 119 public void setTargetClass(Class<?> targetClass) { 120 this.targetClass = targetClass; 121 } 122 123 /** 124 * Set the target package for this instance. Setting either this property or the 125 * {@link #setTargetClass(Class) targetClass} property is required. 126 * <p>If {@link #setTargetClass(Class) targetClass} is set, this property is ignored. 127 */ 128 public void setTargetPackage(String targetPackage) { 129 this.targetPackage = targetPackage; 130 } 131 132 /** 133 * Set the optional binding name for this instance. 134 */ 135 public void setBindingName(String bindingName) { 136 this.bindingName = bindingName; 137 } 138 139 /** 140 * Set the number of nesting indent spaces. Default is {@code -1}, i.e. no indentation. 141 */ 142 public void setIndent(int indent) { 143 this.indent = indent; 144 } 145 146 /** 147 * Set the document encoding using for marshalling. Default is UTF-8. 148 */ 149 public void setEncoding(String encoding) { 150 this.encoding = encoding; 151 } 152 153 @Override 154 protected String getDefaultEncoding() { 155 return this.encoding; 156 } 157 158 /** 159 * Set the document standalone flag for marshalling. By default, this flag is not present. 160 */ 161 public void setStandalone(Boolean standalone) { 162 this.standalone = standalone; 163 } 164 165 /** 166 * Set the root element name for the DTD declaration written when marshalling. 167 * By default, this is {@code null} (i.e. no DTD declaration is written). 168 * <p>If set to a value, the system ID or public ID also need to be set. 169 * @see #setDocTypeSystemId(String) 170 * @see #setDocTypePublicId(String) 171 */ 172 public void setDocTypeRootElementName(String docTypeRootElementName) { 173 this.docTypeRootElementName = docTypeRootElementName; 174 } 175 176 /** 177 * Set the system id for the DTD declaration written when marshalling. 178 * By default, this is {@code null}. Only used when the root element also has been set. 179 * <p>Set either this property or {@code docTypePublicId}, not both. 180 * @see #setDocTypeRootElementName(String) 181 */ 182 public void setDocTypeSystemId(String docTypeSystemId) { 183 this.docTypeSystemId = docTypeSystemId; 184 } 185 186 /** 187 * Set the public id for the DTD declaration written when marshalling. 188 * By default, this is {@code null}. Only used when the root element also has been set. 189 * <p>Set either this property or {@code docTypeSystemId}, not both. 190 * @see #setDocTypeRootElementName(String) 191 */ 192 public void setDocTypePublicId(String docTypePublicId) { 193 this.docTypePublicId = docTypePublicId; 194 } 195 196 /** 197 * Set the internal subset Id for the DTD declaration written when marshalling. 198 * By default, this is {@code null}. Only used when the root element also has been set. 199 * @see #setDocTypeRootElementName(String) 200 */ 201 public void setDocTypeInternalSubset(String docTypeInternalSubset) { 202 this.docTypeInternalSubset = docTypeInternalSubset; 203 } 204 205 206 @Override 207 public void afterPropertiesSet() throws JiBXException { 208 if (this.targetClass != null) { 209 if (StringUtils.hasLength(this.bindingName)) { 210 if (logger.isInfoEnabled()) { 211 logger.info("Configured for target class [" + this.targetClass + 212 "] using binding [" + this.bindingName + "]"); 213 } 214 this.bindingFactory = BindingDirectory.getFactory(this.bindingName, this.targetClass); 215 } 216 else { 217 if (logger.isInfoEnabled()) { 218 logger.info("Configured for target class [" + this.targetClass + "]"); 219 } 220 this.bindingFactory = BindingDirectory.getFactory(this.targetClass); 221 } 222 } 223 else if (this.targetPackage != null) { 224 if (!StringUtils.hasLength(this.bindingName)) { 225 this.bindingName = DEFAULT_BINDING_NAME; 226 } 227 if (logger.isInfoEnabled()) { 228 logger.info("Configured for target package [" + this.targetPackage + 229 "] using binding [" + this.bindingName + "]"); 230 } 231 this.bindingFactory = BindingDirectory.getFactory(this.bindingName, this.targetPackage); 232 } 233 else { 234 throw new IllegalArgumentException("Either 'targetClass' or 'targetPackage' is required"); 235 } 236 } 237 238 239 @Override 240 public boolean supports(Class<?> clazz) { 241 Assert.notNull(clazz, "Class must not be null"); 242 if (this.targetClass != null) { 243 return (this.targetClass == clazz); 244 } 245 String[] mappedClasses = this.bindingFactory.getMappedClasses(); 246 String className = clazz.getName(); 247 for (String mappedClass : mappedClasses) { 248 if (className.equals(mappedClass)) { 249 return true; 250 } 251 } 252 return false; 253 } 254 255 256 // Supported marshalling 257 258 @Override 259 protected void marshalOutputStream(Object graph, OutputStream outputStream) 260 throws XmlMappingException, IOException { 261 try { 262 IMarshallingContext marshallingContext = createMarshallingContext(); 263 marshallingContext.startDocument(this.encoding, this.standalone, outputStream); 264 marshalDocument(marshallingContext, graph); 265 } 266 catch (JiBXException ex) { 267 throw convertJibxException(ex, true); 268 } 269 } 270 271 @Override 272 protected void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException { 273 try { 274 IMarshallingContext marshallingContext = createMarshallingContext(); 275 marshallingContext.startDocument(this.encoding, this.standalone, writer); 276 marshalDocument(marshallingContext, graph); 277 } 278 catch (JiBXException ex) { 279 throw convertJibxException(ex, true); 280 } 281 } 282 283 private void marshalDocument(IMarshallingContext marshallingContext, Object graph) throws IOException, JiBXException { 284 if (StringUtils.hasLength(this.docTypeRootElementName)) { 285 IXMLWriter xmlWriter = marshallingContext.getXmlWriter(); 286 xmlWriter.writeDocType(this.docTypeRootElementName, this.docTypeSystemId, 287 this.docTypePublicId, this.docTypeInternalSubset); 288 } 289 marshallingContext.marshalDocument(graph); 290 } 291 292 293 // Unsupported marshalling 294 295 @Override 296 protected void marshalDomNode(Object graph, Node node) throws XmlMappingException { 297 try { 298 // JiBX does not support DOM natively, so we write to a buffer first, and transform that to the Node 299 Result result = new DOMResult(node); 300 transformAndMarshal(graph, result); 301 } 302 catch (IOException ex) { 303 throw new MarshallingFailureException("JiBX marshalling exception", ex); 304 } 305 } 306 307 @Override 308 protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) { 309 XMLStreamWriter streamWriter = StaxUtils.createEventStreamWriter(eventWriter); 310 marshalXmlStreamWriter(graph, streamWriter); 311 } 312 313 @Override 314 protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException { 315 try { 316 MarshallingContext marshallingContext = (MarshallingContext) createMarshallingContext(); 317 IXMLWriter xmlWriter = new StAXWriter(marshallingContext.getNamespaces(), streamWriter); 318 marshallingContext.setXmlWriter(xmlWriter); 319 marshallingContext.marshalDocument(graph); 320 } 321 catch (JiBXException ex) { 322 throw convertJibxException(ex, false); 323 } 324 } 325 326 @Override 327 protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler) 328 throws XmlMappingException { 329 try { 330 // JiBX does not support SAX natively, so we write to a buffer first, and transform that to the handlers 331 SAXResult saxResult = new SAXResult(contentHandler); 332 saxResult.setLexicalHandler(lexicalHandler); 333 transformAndMarshal(graph, saxResult); 334 } 335 catch (IOException ex) { 336 throw new MarshallingFailureException("JiBX marshalling exception", ex); 337 } 338 } 339 340 private void transformAndMarshal(Object graph, Result result) throws IOException { 341 try { 342 ByteArrayOutputStream os = new ByteArrayOutputStream(1024); 343 marshalOutputStream(graph, os); 344 ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); 345 Transformer transformer = this.transformerFactory.newTransformer(); 346 transformer.transform(new StreamSource(is), result); 347 } 348 catch (TransformerException ex) { 349 throw new MarshallingFailureException( 350 "Could not transform to [" + ClassUtils.getShortName(result.getClass()) + "]", ex); 351 } 352 353 } 354 355 356 // Unmarshalling 357 358 @Override 359 protected Object unmarshalXmlEventReader(XMLEventReader eventReader) { 360 try { 361 XMLStreamReader streamReader = StaxUtils.createEventStreamReader(eventReader); 362 return unmarshalXmlStreamReader(streamReader); 363 } 364 catch (XMLStreamException ex) { 365 return new UnmarshallingFailureException("JiBX unmarshalling exception", ex); 366 } 367 } 368 369 @Override 370 protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) { 371 try { 372 UnmarshallingContext unmarshallingContext = (UnmarshallingContext) createUnmarshallingContext(); 373 IXMLReader xmlReader = new StAXReaderWrapper(streamReader, null, true); 374 unmarshallingContext.setDocument(xmlReader); 375 return unmarshallingContext.unmarshalElement(); 376 } 377 catch (JiBXException ex) { 378 throw convertJibxException(ex, false); 379 } 380 } 381 382 @Override 383 protected Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException { 384 try { 385 IUnmarshallingContext unmarshallingContext = createUnmarshallingContext(); 386 return unmarshallingContext.unmarshalDocument(inputStream, this.encoding); 387 } 388 catch (JiBXException ex) { 389 throw convertJibxException(ex, false); 390 } 391 } 392 393 @Override 394 protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOException { 395 try { 396 IUnmarshallingContext unmarshallingContext = createUnmarshallingContext(); 397 return unmarshallingContext.unmarshalDocument(reader); 398 } 399 catch (JiBXException ex) { 400 throw convertJibxException(ex, false); 401 } 402 } 403 404 405 // Unsupported Unmarshalling 406 407 @Override 408 protected Object unmarshalDomNode(Node node) throws XmlMappingException { 409 try { 410 return transformAndUnmarshal(new DOMSource(node), null); 411 } 412 catch (IOException ex) { 413 throw new UnmarshallingFailureException("JiBX unmarshalling exception", ex); 414 } 415 } 416 417 @Override 418 protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) 419 throws XmlMappingException, IOException { 420 421 return transformAndUnmarshal(new SAXSource(xmlReader, inputSource), inputSource.getEncoding()); 422 } 423 424 private Object transformAndUnmarshal(Source source, String encoding) throws IOException { 425 try { 426 Transformer transformer = this.transformerFactory.newTransformer(); 427 if (encoding != null) { 428 transformer.setOutputProperty(OutputKeys.ENCODING, encoding); 429 } 430 ByteArrayOutputStream os = new ByteArrayOutputStream(1024); 431 transformer.transform(source, new StreamResult(os)); 432 ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); 433 return unmarshalInputStream(is); 434 } 435 catch (TransformerException ex) { 436 throw new MarshallingFailureException( 437 "Could not transform from [" + ClassUtils.getShortName(source.getClass()) + "]", ex); 438 } 439 } 440 441 442 /** 443 * Create a new {@code IMarshallingContext}, configured with the correct indentation. 444 * @return the created marshalling context 445 * @throws JiBXException in case of errors 446 */ 447 protected IMarshallingContext createMarshallingContext() throws JiBXException { 448 IMarshallingContext marshallingContext = this.bindingFactory.createMarshallingContext(); 449 marshallingContext.setIndent(this.indent); 450 return marshallingContext; 451 } 452 453 /** 454 * Create a new {@code IUnmarshallingContext}. 455 * @return the created unmarshalling context 456 * @throws JiBXException in case of errors 457 */ 458 protected IUnmarshallingContext createUnmarshallingContext() throws JiBXException { 459 return this.bindingFactory.createUnmarshallingContext(); 460 } 461 462 /** 463 * Convert the given {@code JiBXException} to an appropriate exception from the 464 * {@code org.springframework.oxm} hierarchy. 465 * <p>A boolean flag is used to indicate whether this exception occurs during marshalling or 466 * unmarshalling, since JiBX itself does not make this distinction in its exception hierarchy. 467 * @param ex {@code JiBXException} that occurred 468 * @param marshalling indicates whether the exception occurs during marshalling ({@code true}), 469 * or unmarshalling ({@code false}) 470 * @return the corresponding {@code XmlMappingException} 471 */ 472 public XmlMappingException convertJibxException(JiBXException ex, boolean marshalling) { 473 if (ex instanceof ValidationException) { 474 return new ValidationFailureException("JiBX validation exception", ex); 475 } 476 else { 477 if (marshalling) { 478 return new MarshallingFailureException("JiBX marshalling exception", ex); 479 } 480 else { 481 return new UnmarshallingFailureException("JiBX unmarshalling exception", ex); 482 } 483 } 484 } 485 486}