001/* 002 * Copyright 2002-2020 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.xstream; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.InputStreamReader; 022import java.io.OutputStream; 023import java.io.OutputStreamWriter; 024import java.io.Reader; 025import java.io.Writer; 026import java.lang.reflect.Constructor; 027import java.util.LinkedHashMap; 028import java.util.List; 029import java.util.Map; 030 031import javax.xml.stream.XMLEventReader; 032import javax.xml.stream.XMLEventWriter; 033import javax.xml.stream.XMLStreamException; 034import javax.xml.stream.XMLStreamReader; 035import javax.xml.stream.XMLStreamWriter; 036import javax.xml.transform.stream.StreamSource; 037 038import com.thoughtworks.xstream.MarshallingStrategy; 039import com.thoughtworks.xstream.XStream; 040import com.thoughtworks.xstream.converters.ConversionException; 041import com.thoughtworks.xstream.converters.Converter; 042import com.thoughtworks.xstream.converters.ConverterLookup; 043import com.thoughtworks.xstream.converters.ConverterMatcher; 044import com.thoughtworks.xstream.converters.ConverterRegistry; 045import com.thoughtworks.xstream.converters.DataHolder; 046import com.thoughtworks.xstream.converters.SingleValueConverter; 047import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; 048import com.thoughtworks.xstream.core.ClassLoaderReference; 049import com.thoughtworks.xstream.core.DefaultConverterLookup; 050import com.thoughtworks.xstream.io.HierarchicalStreamDriver; 051import com.thoughtworks.xstream.io.HierarchicalStreamReader; 052import com.thoughtworks.xstream.io.HierarchicalStreamWriter; 053import com.thoughtworks.xstream.io.StreamException; 054import com.thoughtworks.xstream.io.naming.NameCoder; 055import com.thoughtworks.xstream.io.xml.CompactWriter; 056import com.thoughtworks.xstream.io.xml.DomReader; 057import com.thoughtworks.xstream.io.xml.DomWriter; 058import com.thoughtworks.xstream.io.xml.QNameMap; 059import com.thoughtworks.xstream.io.xml.SaxWriter; 060import com.thoughtworks.xstream.io.xml.StaxReader; 061import com.thoughtworks.xstream.io.xml.StaxWriter; 062import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder; 063import com.thoughtworks.xstream.io.xml.XppDriver; 064import com.thoughtworks.xstream.mapper.CannotResolveClassException; 065import com.thoughtworks.xstream.mapper.Mapper; 066import com.thoughtworks.xstream.mapper.MapperWrapper; 067import org.w3c.dom.Document; 068import org.w3c.dom.Element; 069import org.w3c.dom.Node; 070import org.xml.sax.ContentHandler; 071import org.xml.sax.InputSource; 072import org.xml.sax.XMLReader; 073import org.xml.sax.ext.LexicalHandler; 074 075import org.springframework.beans.factory.BeanClassLoaderAware; 076import org.springframework.beans.factory.InitializingBean; 077import org.springframework.lang.Nullable; 078import org.springframework.oxm.MarshallingFailureException; 079import org.springframework.oxm.UncategorizedMappingException; 080import org.springframework.oxm.UnmarshallingFailureException; 081import org.springframework.oxm.XmlMappingException; 082import org.springframework.oxm.support.AbstractMarshaller; 083import org.springframework.util.ClassUtils; 084import org.springframework.util.ObjectUtils; 085import org.springframework.util.StringUtils; 086import org.springframework.util.function.SingletonSupplier; 087import org.springframework.util.xml.StaxUtils; 088 089/** 090 * Implementation of the {@code Marshaller} interface for XStream. 091 * 092 * <p>By default, XStream does not require any further configuration and can (un)marshal 093 * any class on the classpath. As such, it is <b>not recommended to use the 094 * {@code XStreamMarshaller} to unmarshal XML from external sources</b> (i.e. the Web), 095 * as this can result in <b>security vulnerabilities</b>. If you do use the 096 * {@code XStreamMarshaller} to unmarshal external XML, set the 097 * {@link #setSupportedClasses(Class[]) supportedClasses} and 098 * {@link #setConverters(ConverterMatcher[]) converters} properties (possibly using 099 * a {@link CatchAllConverter}) or override the {@link #customizeXStream(XStream)} 100 * method to make sure it only accepts the classes you want it to support. 101 * 102 * <p>Due to XStream's API, it is required to set the encoding used for writing to 103 * OutputStreams. It defaults to {@code UTF-8}. 104 * 105 * <p><b>NOTE:</b> XStream is an XML serialization library, not a data binding library. 106 * Therefore, it has limited namespace support. As such, it is rather unsuitable for 107 * usage within Web Services. 108 * 109 * <p>This marshaller requires XStream 1.4.5 or higher, as of Spring 4.3. 110 * Note that {@link XStream} construction has been reworked in 4.0, with the 111 * stream driver and the class loader getting passed into XStream itself now. 112 * 113 * @author Peter Meijer 114 * @author Arjen Poutsma 115 * @author Juergen Hoeller 116 * @author Sam Brannen 117 * @since 3.0 118 */ 119public class XStreamMarshaller extends AbstractMarshaller implements BeanClassLoaderAware, InitializingBean { 120 121 /** 122 * The default encoding used for stream access: UTF-8. 123 */ 124 public static final String DEFAULT_ENCODING = "UTF-8"; 125 126 127 @Nullable 128 private ReflectionProvider reflectionProvider; 129 130 @Nullable 131 private HierarchicalStreamDriver streamDriver; 132 133 @Nullable 134 private HierarchicalStreamDriver defaultDriver; 135 136 @Nullable 137 private Mapper mapper; 138 139 @Nullable 140 private Class<? extends MapperWrapper>[] mapperWrappers; 141 142 private ConverterLookup converterLookup = new DefaultConverterLookup(); 143 144 private ConverterRegistry converterRegistry = (ConverterRegistry) this.converterLookup; 145 146 @Nullable 147 private ConverterMatcher[] converters; 148 149 @Nullable 150 private MarshallingStrategy marshallingStrategy; 151 152 @Nullable 153 private Integer mode; 154 155 @Nullable 156 private Map<String, ?> aliases; 157 158 @Nullable 159 private Map<String, ?> aliasesByType; 160 161 @Nullable 162 private Map<String, String> fieldAliases; 163 164 @Nullable 165 private Class<?>[] useAttributeForTypes; 166 167 @Nullable 168 private Map<?, ?> useAttributeFor; 169 170 @Nullable 171 private Map<Class<?>, String> implicitCollections; 172 173 @Nullable 174 private Map<Class<?>, String> omittedFields; 175 176 @Nullable 177 private Class<?>[] annotatedClasses; 178 179 private boolean autodetectAnnotations; 180 181 private String encoding = DEFAULT_ENCODING; 182 183 private NameCoder nameCoder = new XmlFriendlyNameCoder(); 184 185 @Nullable 186 private Class<?>[] supportedClasses; 187 188 @Nullable 189 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 190 191 private final SingletonSupplier<XStream> xstream = SingletonSupplier.of(this::buildXStream); 192 193 194 /** 195 * Set a custom XStream {@link ReflectionProvider} to use. 196 * @since 4.0 197 */ 198 public void setReflectionProvider(ReflectionProvider reflectionProvider) { 199 this.reflectionProvider = reflectionProvider; 200 } 201 202 /** 203 * Set a XStream {@link HierarchicalStreamDriver} to be used for readers and writers. 204 * <p>As of Spring 4.0, this stream driver will also be passed to the {@link XStream} 205 * constructor and therefore used by streaming-related native API methods themselves. 206 */ 207 public void setStreamDriver(HierarchicalStreamDriver streamDriver) { 208 this.streamDriver = streamDriver; 209 this.defaultDriver = streamDriver; 210 } 211 212 private HierarchicalStreamDriver getDefaultDriver() { 213 if (this.defaultDriver == null) { 214 this.defaultDriver = new XppDriver(); 215 } 216 return this.defaultDriver; 217 } 218 219 /** 220 * Set a custom XStream {@link Mapper} to use. 221 * @since 4.0 222 */ 223 public void setMapper(Mapper mapper) { 224 this.mapper = mapper; 225 } 226 227 /** 228 * Set one or more custom XStream {@link MapperWrapper} classes. 229 * Each of those classes needs to have a constructor with a single argument 230 * of type {@link Mapper} or {@link MapperWrapper}. 231 * @since 4.0 232 */ 233 @SuppressWarnings("unchecked") 234 public void setMapperWrappers(Class<? extends MapperWrapper>... mapperWrappers) { 235 this.mapperWrappers = mapperWrappers; 236 } 237 238 /** 239 * Set a custom XStream {@link ConverterLookup} to use. 240 * Also used as {@link ConverterRegistry} if the given reference implements it as well. 241 * @since 4.0 242 * @see DefaultConverterLookup 243 */ 244 public void setConverterLookup(ConverterLookup converterLookup) { 245 this.converterLookup = converterLookup; 246 if (converterLookup instanceof ConverterRegistry) { 247 this.converterRegistry = (ConverterRegistry) converterLookup; 248 } 249 } 250 251 /** 252 * Set a custom XStream {@link ConverterRegistry} to use. 253 * @since 4.0 254 * @see #setConverterLookup 255 * @see DefaultConverterLookup 256 */ 257 public void setConverterRegistry(ConverterRegistry converterRegistry) { 258 this.converterRegistry = converterRegistry; 259 } 260 261 /** 262 * Set the {@code Converters} or {@code SingleValueConverters} to be registered 263 * with the {@code XStream} instance. 264 * @see Converter 265 * @see SingleValueConverter 266 */ 267 public void setConverters(ConverterMatcher... converters) { 268 this.converters = converters; 269 } 270 271 /** 272 * Set a custom XStream {@link MarshallingStrategy} to use. 273 * @since 4.0 274 */ 275 public void setMarshallingStrategy(MarshallingStrategy marshallingStrategy) { 276 this.marshallingStrategy = marshallingStrategy; 277 } 278 279 /** 280 * Set the XStream mode to use. 281 * @see XStream#ID_REFERENCES 282 * @see XStream#NO_REFERENCES 283 */ 284 public void setMode(int mode) { 285 this.mode = mode; 286 } 287 288 /** 289 * Set the alias/type map, consisting of string aliases mapped to classes. 290 * <p>Keys are aliases; values are either {@code Class} instances, or String class names. 291 * @see XStream#alias(String, Class) 292 */ 293 public void setAliases(Map<String, ?> aliases) { 294 this.aliases = aliases; 295 } 296 297 /** 298 * Set the <em>aliases by type</em> map, consisting of string aliases mapped to classes. 299 * <p>Any class that is assignable to this type will be aliased to the same name. 300 * Keys are aliases; values are either {@code Class} instances, or String class names. 301 * @see XStream#aliasType(String, Class) 302 */ 303 public void setAliasesByType(Map<String, ?> aliasesByType) { 304 this.aliasesByType = aliasesByType; 305 } 306 307 /** 308 * Set the field alias/type map, consisting of field names. 309 * @see XStream#aliasField(String, Class, String) 310 */ 311 public void setFieldAliases(Map<String, String> fieldAliases) { 312 this.fieldAliases = fieldAliases; 313 } 314 315 /** 316 * Set types to use XML attributes for. 317 * @see XStream#useAttributeFor(Class) 318 */ 319 public void setUseAttributeForTypes(Class<?>... useAttributeForTypes) { 320 this.useAttributeForTypes = useAttributeForTypes; 321 } 322 323 /** 324 * Set the types to use XML attributes for. The given map can contain 325 * either {@code <String, Class>} pairs, in which case 326 * {@link XStream#useAttributeFor(String, Class)} is called. 327 * Alternatively, the map can contain {@code <Class, String>} 328 * or {@code <Class, List<String>>} pairs, which results 329 * in {@link XStream#useAttributeFor(Class, String)} calls. 330 */ 331 public void setUseAttributeFor(Map<?, ?> useAttributeFor) { 332 this.useAttributeFor = useAttributeFor; 333 } 334 335 /** 336 * Specify implicit collection fields, as a Map consisting of {@code Class} instances 337 * mapped to comma separated collection field names. 338 * @see XStream#addImplicitCollection(Class, String) 339 */ 340 public void setImplicitCollections(Map<Class<?>, String> implicitCollections) { 341 this.implicitCollections = implicitCollections; 342 } 343 344 /** 345 * Specify omitted fields, as a Map consisting of {@code Class} instances 346 * mapped to comma separated field names. 347 * @see XStream#omitField(Class, String) 348 */ 349 public void setOmittedFields(Map<Class<?>, String> omittedFields) { 350 this.omittedFields = omittedFields; 351 } 352 353 /** 354 * Set annotated classes for which aliases will be read from class-level annotation metadata. 355 * @see XStream#processAnnotations(Class[]) 356 */ 357 public void setAnnotatedClasses(Class<?>... annotatedClasses) { 358 this.annotatedClasses = annotatedClasses; 359 } 360 361 /** 362 * Activate XStream's autodetection mode. 363 * <p><b>Note</b>: Autodetection implies that the XStream instance is being configured while 364 * it is processing the XML streams, and thus introduces a potential concurrency problem. 365 * @see XStream#autodetectAnnotations(boolean) 366 */ 367 public void setAutodetectAnnotations(boolean autodetectAnnotations) { 368 this.autodetectAnnotations = autodetectAnnotations; 369 } 370 371 /** 372 * Set the encoding to be used for stream access. 373 * @see #DEFAULT_ENCODING 374 */ 375 public void setEncoding(String encoding) { 376 this.encoding = encoding; 377 } 378 379 @Override 380 protected String getDefaultEncoding() { 381 return this.encoding; 382 } 383 384 /** 385 * Set a custom XStream {@link NameCoder} to use. 386 * The default is an {@link XmlFriendlyNameCoder}. 387 * @since 4.0.4 388 */ 389 public void setNameCoder(NameCoder nameCoder) { 390 this.nameCoder = nameCoder; 391 } 392 393 /** 394 * Set the classes supported by this marshaller. 395 * <p>If this property is empty (the default), all classes are supported. 396 * @see #supports(Class) 397 */ 398 public void setSupportedClasses(Class<?>... supportedClasses) { 399 this.supportedClasses = supportedClasses; 400 } 401 402 @Override 403 public void setBeanClassLoader(ClassLoader classLoader) { 404 this.beanClassLoader = classLoader; 405 } 406 407 408 @Override 409 public void afterPropertiesSet() { 410 // no-op due to use of SingletonSupplier for the XStream field. 411 } 412 413 /** 414 * Build the native XStream delegate to be used by this marshaller, 415 * delegating to {@link #constructXStream}, {@link #configureXStream}, 416 * and {@link #customizeXStream}. 417 */ 418 protected XStream buildXStream() { 419 XStream xstream = constructXStream(); 420 configureXStream(xstream); 421 customizeXStream(xstream); 422 return xstream; 423 } 424 425 /** 426 * Construct an XStream instance, either using one of the 427 * standard constructors or creating a custom subclass. 428 * @return the {@code XStream} instance 429 */ 430 protected XStream constructXStream() { 431 return new XStream(this.reflectionProvider, getDefaultDriver(), new ClassLoaderReference(this.beanClassLoader), 432 this.mapper, this.converterLookup, this.converterRegistry) { 433 @Override 434 protected MapperWrapper wrapMapper(MapperWrapper next) { 435 MapperWrapper mapperToWrap = next; 436 if (mapperWrappers != null) { 437 for (Class<? extends MapperWrapper> mapperWrapper : mapperWrappers) { 438 Constructor<? extends MapperWrapper> ctor; 439 try { 440 ctor = mapperWrapper.getConstructor(Mapper.class); 441 } 442 catch (NoSuchMethodException ex) { 443 try { 444 ctor = mapperWrapper.getConstructor(MapperWrapper.class); 445 } 446 catch (NoSuchMethodException ex2) { 447 throw new IllegalStateException("No appropriate MapperWrapper constructor found: " + mapperWrapper); 448 } 449 } 450 try { 451 mapperToWrap = ctor.newInstance(mapperToWrap); 452 } 453 catch (Throwable ex) { 454 throw new IllegalStateException("Failed to construct MapperWrapper: " + mapperWrapper); 455 } 456 } 457 } 458 return mapperToWrap; 459 } 460 }; 461 } 462 463 /** 464 * Configure the XStream instance with this marshaller's bean properties. 465 * @param xstream the {@code XStream} instance 466 */ 467 protected void configureXStream(XStream xstream) { 468 if (this.converters != null) { 469 for (int i = 0; i < this.converters.length; i++) { 470 if (this.converters[i] instanceof Converter) { 471 xstream.registerConverter((Converter) this.converters[i], i); 472 } 473 else if (this.converters[i] instanceof SingleValueConverter) { 474 xstream.registerConverter((SingleValueConverter) this.converters[i], i); 475 } 476 else { 477 throw new IllegalArgumentException("Invalid ConverterMatcher [" + this.converters[i] + "]"); 478 } 479 } 480 } 481 482 if (this.marshallingStrategy != null) { 483 xstream.setMarshallingStrategy(this.marshallingStrategy); 484 } 485 if (this.mode != null) { 486 xstream.setMode(this.mode); 487 } 488 489 try { 490 if (this.aliases != null) { 491 Map<String, Class<?>> classMap = toClassMap(this.aliases); 492 classMap.forEach(xstream::alias); 493 } 494 if (this.aliasesByType != null) { 495 Map<String, Class<?>> classMap = toClassMap(this.aliasesByType); 496 classMap.forEach(xstream::aliasType); 497 } 498 if (this.fieldAliases != null) { 499 for (Map.Entry<String, String> entry : this.fieldAliases.entrySet()) { 500 String alias = entry.getValue(); 501 String field = entry.getKey(); 502 int idx = field.lastIndexOf('.'); 503 if (idx != -1) { 504 String className = field.substring(0, idx); 505 Class<?> clazz = ClassUtils.forName(className, this.beanClassLoader); 506 String fieldName = field.substring(idx + 1); 507 xstream.aliasField(alias, clazz, fieldName); 508 } 509 else { 510 throw new IllegalArgumentException("Field name [" + field + "] does not contain '.'"); 511 } 512 } 513 } 514 } 515 catch (ClassNotFoundException ex) { 516 throw new IllegalStateException("Failed to load specified alias class", ex); 517 } 518 519 if (this.useAttributeForTypes != null) { 520 for (Class<?> type : this.useAttributeForTypes) { 521 xstream.useAttributeFor(type); 522 } 523 } 524 if (this.useAttributeFor != null) { 525 for (Map.Entry<?, ?> entry : this.useAttributeFor.entrySet()) { 526 if (entry.getKey() instanceof String) { 527 if (entry.getValue() instanceof Class) { 528 xstream.useAttributeFor((String) entry.getKey(), (Class<?>) entry.getValue()); 529 } 530 else { 531 throw new IllegalArgumentException( 532 "'useAttributesFor' takes Map<String, Class> when using a map key of type String"); 533 } 534 } 535 else if (entry.getKey() instanceof Class) { 536 Class<?> key = (Class<?>) entry.getKey(); 537 if (entry.getValue() instanceof String) { 538 xstream.useAttributeFor(key, (String) entry.getValue()); 539 } 540 else if (entry.getValue() instanceof List) { 541 @SuppressWarnings("unchecked") 542 List<Object> listValue = (List<Object>) entry.getValue(); 543 for (Object element : listValue) { 544 if (element instanceof String) { 545 xstream.useAttributeFor(key, (String) element); 546 } 547 } 548 } 549 else { 550 throw new IllegalArgumentException("'useAttributesFor' property takes either Map<Class, String> " + 551 "or Map<Class, List<String>> when using a map key of type Class"); 552 } 553 } 554 else { 555 throw new IllegalArgumentException( 556 "'useAttributesFor' property takes either a map key of type String or Class"); 557 } 558 } 559 } 560 561 if (this.implicitCollections != null) { 562 this.implicitCollections.forEach((key, fields) -> { 563 String[] collectionFields = StringUtils.commaDelimitedListToStringArray(fields); 564 for (String collectionField : collectionFields) { 565 xstream.addImplicitCollection(key, collectionField); 566 } 567 }); 568 } 569 if (this.omittedFields != null) { 570 this.omittedFields.forEach((key, value) -> { 571 String[] fields = StringUtils.commaDelimitedListToStringArray(value); 572 for (String field : fields) { 573 xstream.omitField(key, field); 574 } 575 }); 576 } 577 578 if (this.annotatedClasses != null) { 579 xstream.processAnnotations(this.annotatedClasses); 580 } 581 if (this.autodetectAnnotations) { 582 xstream.autodetectAnnotations(true); 583 } 584 } 585 586 private Map<String, Class<?>> toClassMap(Map<String, ?> map) throws ClassNotFoundException { 587 Map<String, Class<?>> result = new LinkedHashMap<>(map.size()); 588 for (Map.Entry<String, ?> entry : map.entrySet()) { 589 String key = entry.getKey(); 590 Object value = entry.getValue(); 591 Class<?> type; 592 if (value instanceof Class) { 593 type = (Class<?>) value; 594 } 595 else if (value instanceof String) { 596 String className = (String) value; 597 type = ClassUtils.forName(className, this.beanClassLoader); 598 } 599 else { 600 throw new IllegalArgumentException("Unknown value [" + value + "] - expected String or Class"); 601 } 602 result.put(key, type); 603 } 604 return result; 605 } 606 607 /** 608 * Template to allow for customizing the given {@link XStream}. 609 * <p>The default implementation is empty. 610 * @param xstream the {@code XStream} instance 611 */ 612 protected void customizeXStream(XStream xstream) { 613 } 614 615 /** 616 * Return the native XStream delegate used by this marshaller. 617 * <p><b>NOTE: This method has been marked as final as of Spring 4.0.</b> 618 * It can be used to access the fully configured XStream for marshalling 619 * but not configuration purposes anymore. 620 * <p>As of Spring Framework 5.1.16, creation of the {@link XStream} instance 621 * returned by this method is thread safe. 622 */ 623 public final XStream getXStream() { 624 return this.xstream.obtain(); 625 } 626 627 628 @Override 629 public boolean supports(Class<?> clazz) { 630 if (ObjectUtils.isEmpty(this.supportedClasses)) { 631 return true; 632 } 633 else { 634 for (Class<?> supportedClass : this.supportedClasses) { 635 if (supportedClass.isAssignableFrom(clazz)) { 636 return true; 637 } 638 } 639 return false; 640 } 641 } 642 643 644 // Marshalling 645 646 @Override 647 protected void marshalDomNode(Object graph, Node node) throws XmlMappingException { 648 HierarchicalStreamWriter streamWriter; 649 if (node instanceof Document) { 650 streamWriter = new DomWriter((Document) node, this.nameCoder); 651 } 652 else if (node instanceof Element) { 653 streamWriter = new DomWriter((Element) node, node.getOwnerDocument(), this.nameCoder); 654 } 655 else { 656 throw new IllegalArgumentException("DOMResult contains neither Document nor Element"); 657 } 658 doMarshal(graph, streamWriter, null); 659 } 660 661 @Override 662 protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) throws XmlMappingException { 663 ContentHandler contentHandler = StaxUtils.createContentHandler(eventWriter); 664 LexicalHandler lexicalHandler = null; 665 if (contentHandler instanceof LexicalHandler) { 666 lexicalHandler = (LexicalHandler) contentHandler; 667 } 668 marshalSaxHandlers(graph, contentHandler, lexicalHandler); 669 } 670 671 @Override 672 protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException { 673 try { 674 doMarshal(graph, new StaxWriter(new QNameMap(), streamWriter, this.nameCoder), null); 675 } 676 catch (XMLStreamException ex) { 677 throw convertXStreamException(ex, true); 678 } 679 } 680 681 @Override 682 protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, @Nullable LexicalHandler lexicalHandler) 683 throws XmlMappingException { 684 685 SaxWriter saxWriter = new SaxWriter(this.nameCoder); 686 saxWriter.setContentHandler(contentHandler); 687 doMarshal(graph, saxWriter, null); 688 } 689 690 @Override 691 public void marshalOutputStream(Object graph, OutputStream outputStream) throws XmlMappingException, IOException { 692 marshalOutputStream(graph, outputStream, null); 693 } 694 695 public void marshalOutputStream(Object graph, OutputStream outputStream, @Nullable DataHolder dataHolder) 696 throws XmlMappingException, IOException { 697 698 if (this.streamDriver != null) { 699 doMarshal(graph, this.streamDriver.createWriter(outputStream), dataHolder); 700 } 701 else { 702 marshalWriter(graph, new OutputStreamWriter(outputStream, this.encoding), dataHolder); 703 } 704 } 705 706 @Override 707 public void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException { 708 marshalWriter(graph, writer, null); 709 } 710 711 public void marshalWriter(Object graph, Writer writer, @Nullable DataHolder dataHolder) 712 throws XmlMappingException, IOException { 713 714 if (this.streamDriver != null) { 715 doMarshal(graph, this.streamDriver.createWriter(writer), dataHolder); 716 } 717 else { 718 doMarshal(graph, new CompactWriter(writer), dataHolder); 719 } 720 } 721 722 /** 723 * Marshals the given graph to the given XStream HierarchicalStreamWriter. 724 * Converts exceptions using {@link #convertXStreamException}. 725 */ 726 private void doMarshal(Object graph, HierarchicalStreamWriter streamWriter, @Nullable DataHolder dataHolder) { 727 try { 728 getXStream().marshal(graph, streamWriter, dataHolder); 729 } 730 catch (Exception ex) { 731 throw convertXStreamException(ex, true); 732 } 733 finally { 734 try { 735 streamWriter.flush(); 736 } 737 catch (Exception ex) { 738 logger.debug("Could not flush HierarchicalStreamWriter", ex); 739 } 740 } 741 } 742 743 744 // Unmarshalling 745 746 @Override 747 protected Object unmarshalStreamSource(StreamSource streamSource) throws XmlMappingException, IOException { 748 if (streamSource.getInputStream() != null) { 749 return unmarshalInputStream(streamSource.getInputStream()); 750 } 751 else if (streamSource.getReader() != null) { 752 return unmarshalReader(streamSource.getReader()); 753 } 754 else { 755 throw new IllegalArgumentException("StreamSource contains neither InputStream nor Reader"); 756 } 757 } 758 759 @Override 760 protected Object unmarshalDomNode(Node node) throws XmlMappingException { 761 HierarchicalStreamReader streamReader; 762 if (node instanceof Document) { 763 streamReader = new DomReader((Document) node, this.nameCoder); 764 } 765 else if (node instanceof Element) { 766 streamReader = new DomReader((Element) node, this.nameCoder); 767 } 768 else { 769 throw new IllegalArgumentException("DOMSource contains neither Document nor Element"); 770 } 771 return doUnmarshal(streamReader, null); 772 } 773 774 @Override 775 protected Object unmarshalXmlEventReader(XMLEventReader eventReader) throws XmlMappingException { 776 try { 777 XMLStreamReader streamReader = StaxUtils.createEventStreamReader(eventReader); 778 return unmarshalXmlStreamReader(streamReader); 779 } 780 catch (XMLStreamException ex) { 781 throw convertXStreamException(ex, false); 782 } 783 } 784 785 @Override 786 protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) throws XmlMappingException { 787 return doUnmarshal(new StaxReader(new QNameMap(), streamReader, this.nameCoder), null); 788 } 789 790 @Override 791 protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) 792 throws XmlMappingException, IOException { 793 794 throw new UnsupportedOperationException( 795 "XStreamMarshaller does not support unmarshalling using SAX XMLReaders"); 796 } 797 798 @Override 799 public Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException { 800 return unmarshalInputStream(inputStream, null); 801 } 802 803 public Object unmarshalInputStream(InputStream inputStream, @Nullable DataHolder dataHolder) throws XmlMappingException, IOException { 804 if (this.streamDriver != null) { 805 return doUnmarshal(this.streamDriver.createReader(inputStream), dataHolder); 806 } 807 else { 808 return unmarshalReader(new InputStreamReader(inputStream, this.encoding), dataHolder); 809 } 810 } 811 812 @Override 813 public Object unmarshalReader(Reader reader) throws XmlMappingException, IOException { 814 return unmarshalReader(reader, null); 815 } 816 817 public Object unmarshalReader(Reader reader, @Nullable DataHolder dataHolder) throws XmlMappingException, IOException { 818 return doUnmarshal(getDefaultDriver().createReader(reader), dataHolder); 819 } 820 821 /** 822 * Unmarshals the given graph to the given XStream HierarchicalStreamWriter. 823 * Converts exceptions using {@link #convertXStreamException}. 824 */ 825 private Object doUnmarshal(HierarchicalStreamReader streamReader, @Nullable DataHolder dataHolder) { 826 try { 827 return getXStream().unmarshal(streamReader, null, dataHolder); 828 } 829 catch (Exception ex) { 830 throw convertXStreamException(ex, false); 831 } 832 } 833 834 835 /** 836 * Convert the given XStream exception to an appropriate exception from the 837 * {@code org.springframework.oxm} hierarchy. 838 * <p>A boolean flag is used to indicate whether this exception occurs during marshalling or 839 * unmarshalling, since XStream itself does not make this distinction in its exception hierarchy. 840 * @param ex the XStream exception that occurred 841 * @param marshalling indicates whether the exception occurs during marshalling ({@code true}), 842 * or unmarshalling ({@code false}) 843 * @return the corresponding {@code XmlMappingException} 844 */ 845 protected XmlMappingException convertXStreamException(Exception ex, boolean marshalling) { 846 if (ex instanceof StreamException || ex instanceof CannotResolveClassException || 847 ex instanceof ConversionException) { 848 if (marshalling) { 849 return new MarshallingFailureException("XStream marshalling exception", ex); 850 } 851 else { 852 return new UnmarshallingFailureException("XStream unmarshalling exception", ex); 853 } 854 } 855 else { 856 // fallback 857 return new UncategorizedMappingException("Unknown XStream exception", ex); 858 } 859 } 860 861}