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