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