1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.oxm.xstream;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.InputStreamReader;
22 import java.io.OutputStream;
23 import java.io.OutputStreamWriter;
24 import java.io.Reader;
25 import java.io.Writer;
26 import java.lang.reflect.Constructor;
27 import java.util.LinkedHashMap;
28 import java.util.List;
29 import java.util.Map;
30 import javax.xml.stream.XMLEventReader;
31 import javax.xml.stream.XMLEventWriter;
32 import javax.xml.stream.XMLStreamException;
33 import javax.xml.stream.XMLStreamReader;
34 import javax.xml.stream.XMLStreamWriter;
35 import javax.xml.transform.stream.StreamSource;
36
37 import com.thoughtworks.xstream.MarshallingStrategy;
38 import com.thoughtworks.xstream.XStream;
39 import com.thoughtworks.xstream.converters.ConversionException;
40 import com.thoughtworks.xstream.converters.Converter;
41 import com.thoughtworks.xstream.converters.ConverterLookup;
42 import com.thoughtworks.xstream.converters.ConverterMatcher;
43 import com.thoughtworks.xstream.converters.ConverterRegistry;
44 import com.thoughtworks.xstream.converters.DataHolder;
45 import com.thoughtworks.xstream.converters.SingleValueConverter;
46 import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
47 import com.thoughtworks.xstream.core.DefaultConverterLookup;
48 import com.thoughtworks.xstream.core.util.CompositeClassLoader;
49 import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
50 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
51 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
52 import com.thoughtworks.xstream.io.StreamException;
53 import com.thoughtworks.xstream.io.naming.NameCoder;
54 import com.thoughtworks.xstream.io.xml.CompactWriter;
55 import com.thoughtworks.xstream.io.xml.DomReader;
56 import com.thoughtworks.xstream.io.xml.DomWriter;
57 import com.thoughtworks.xstream.io.xml.QNameMap;
58 import com.thoughtworks.xstream.io.xml.SaxWriter;
59 import com.thoughtworks.xstream.io.xml.StaxReader;
60 import com.thoughtworks.xstream.io.xml.StaxWriter;
61 import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
62 import com.thoughtworks.xstream.io.xml.XppDriver;
63 import com.thoughtworks.xstream.mapper.CannotResolveClassException;
64 import com.thoughtworks.xstream.mapper.Mapper;
65 import com.thoughtworks.xstream.mapper.MapperWrapper;
66 import org.w3c.dom.Document;
67 import org.w3c.dom.Element;
68 import org.w3c.dom.Node;
69 import org.xml.sax.ContentHandler;
70 import org.xml.sax.InputSource;
71 import org.xml.sax.XMLReader;
72 import org.xml.sax.ext.LexicalHandler;
73
74 import org.springframework.beans.factory.BeanClassLoaderAware;
75 import org.springframework.beans.factory.InitializingBean;
76 import org.springframework.oxm.MarshallingFailureException;
77 import org.springframework.oxm.UncategorizedMappingException;
78 import org.springframework.oxm.UnmarshallingFailureException;
79 import org.springframework.oxm.XmlMappingException;
80 import org.springframework.oxm.support.AbstractMarshaller;
81 import org.springframework.util.Assert;
82 import org.springframework.util.ClassUtils;
83 import org.springframework.util.ObjectUtils;
84 import org.springframework.util.StringUtils;
85 import org.springframework.util.xml.StaxUtils;
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116 public class XStreamMarshaller extends AbstractMarshaller implements InitializingBean, BeanClassLoaderAware {
117
118
119
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<?>[] 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
175
176
177 public void setReflectionProvider(ReflectionProvider reflectionProvider) {
178 this.reflectionProvider = reflectionProvider;
179 }
180
181
182
183
184
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
200
201
202 public void setMapper(Mapper mapper) {
203 this.mapper = mapper;
204 }
205
206
207
208
209
210
211
212 public void setMapperWrappers(Class<?>... mapperWrappers) {
213 this.mapperWrappers = mapperWrappers;
214 }
215
216
217
218
219
220
221
222 public void setConverterLookup(ConverterLookup converterLookup) {
223 this.converterLookup = converterLookup;
224 if (converterLookup instanceof ConverterRegistry) {
225 this.converterRegistry = (ConverterRegistry) converterLookup;
226 }
227 }
228
229
230
231
232
233
234
235 public void setConverterRegistry(ConverterRegistry converterRegistry) {
236 this.converterRegistry = converterRegistry;
237 }
238
239
240
241
242
243
244
245 public void setConverters(ConverterMatcher... converters) {
246 this.converters = converters;
247 }
248
249
250
251
252
253 public void setMarshallingStrategy(MarshallingStrategy marshallingStrategy) {
254 this.marshallingStrategy = marshallingStrategy;
255 }
256
257
258
259
260
261
262 public void setMode(int mode) {
263 this.mode = mode;
264 }
265
266
267
268
269
270
271 public void setAliases(Map<String, ?> aliases) {
272 this.aliases = aliases;
273 }
274
275
276
277
278
279
280
281 public void setAliasesByType(Map<String, ?> aliasesByType) {
282 this.aliasesByType = aliasesByType;
283 }
284
285
286
287
288
289 public void setFieldAliases(Map<String, String> fieldAliases) {
290 this.fieldAliases = fieldAliases;
291 }
292
293
294
295
296
297 public void setUseAttributeForTypes(Class<?>... useAttributeForTypes) {
298 this.useAttributeForTypes = useAttributeForTypes;
299 }
300
301
302
303
304
305
306
307
308
309 public void setUseAttributeFor(Map<?, ?> useAttributeFor) {
310 this.useAttributeFor = useAttributeFor;
311 }
312
313
314
315
316
317
318 public void setImplicitCollections(Map<Class<?>, String> implicitCollections) {
319 this.implicitCollections = implicitCollections;
320 }
321
322
323
324
325
326
327 public void setOmittedFields(Map<Class<?>, String> omittedFields) {
328 this.omittedFields = omittedFields;
329 }
330
331
332
333
334
335 public void setAnnotatedClasses(Class<?>... annotatedClasses) {
336 this.annotatedClasses = annotatedClasses;
337 }
338
339
340
341
342
343
344
345 public void setAutodetectAnnotations(boolean autodetectAnnotations) {
346 this.autodetectAnnotations = autodetectAnnotations;
347 }
348
349
350
351
352
353 public void setEncoding(String encoding) {
354 this.encoding = encoding;
355 }
356
357 @Override
358 protected String getDefaultEncoding() {
359 return this.encoding;
360 }
361
362
363
364
365
366
367 public void setNameCoder(NameCoder nameCoder) {
368 this.nameCoder = nameCoder;
369 }
370
371
372
373
374
375
376 public void setSupportedClasses(Class<?>... supportedClasses) {
377 this.supportedClasses = supportedClasses;
378 }
379
380 @Override
381 public void setBeanClassLoader(ClassLoader classLoader) {
382 this.beanClassLoader = classLoader;
383 }
384
385
386 @Override
387 public void afterPropertiesSet() {
388 this.xstream = buildXStream();
389 }
390
391
392
393
394
395
396 protected XStream buildXStream() {
397 XStream xstream = constructXStream();
398 configureXStream(xstream);
399 customizeXStream(xstream);
400 return xstream;
401 }
402
403
404
405
406
407
408 @SuppressWarnings("deprecation")
409 protected XStream constructXStream() {
410
411
412 return new XStream(this.reflectionProvider, getDefaultDriver(),
413 this.beanClassLoader, this.mapper, this.converterLookup, this.converterRegistry) {
414 @Override
415 protected MapperWrapper wrapMapper(MapperWrapper next) {
416 MapperWrapper mapperToWrap = next;
417 if (mapperWrappers != null) {
418 for (Class<?> mapperWrapper : mapperWrappers) {
419 Assert.isAssignable(MapperWrapper.class, mapperWrapper);
420 Constructor<?> ctor;
421 try {
422 ctor = mapperWrapper.getConstructor(Mapper.class);
423 }
424 catch (NoSuchMethodException ex) {
425 try {
426 ctor = mapperWrapper.getConstructor(MapperWrapper.class);
427 }
428 catch (NoSuchMethodException ex2) {
429 throw new IllegalStateException("No appropriate MapperWrapper constructor found: " + mapperWrapper);
430 }
431 }
432 try {
433 mapperToWrap = (MapperWrapper) ctor.newInstance(mapperToWrap);
434 }
435 catch (Exception ex) {
436 throw new IllegalStateException("Failed to construct MapperWrapper: " + mapperWrapper);
437 }
438 }
439 }
440 return mapperToWrap;
441 }
442 };
443 }
444
445
446
447
448
449 protected void configureXStream(XStream xstream) {
450 if (this.converters != null) {
451 for (int i = 0; i < this.converters.length; i++) {
452 if (this.converters[i] instanceof Converter) {
453 xstream.registerConverter((Converter) this.converters[i], i);
454 }
455 else if (this.converters[i] instanceof SingleValueConverter) {
456 xstream.registerConverter((SingleValueConverter) this.converters[i], i);
457 }
458 else {
459 throw new IllegalArgumentException("Invalid ConverterMatcher [" + this.converters[i] + "]");
460 }
461 }
462 }
463
464 if (this.marshallingStrategy != null) {
465 xstream.setMarshallingStrategy(this.marshallingStrategy);
466 }
467 if (this.mode != null) {
468 xstream.setMode(this.mode);
469 }
470
471 try {
472 if (this.aliases != null) {
473 Map<String, Class<?>> classMap = toClassMap(this.aliases);
474 for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) {
475 xstream.alias(entry.getKey(), entry.getValue());
476 }
477 }
478 if (this.aliasesByType != null) {
479 Map<String, Class<?>> classMap = toClassMap(this.aliasesByType);
480 for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) {
481 xstream.aliasType(entry.getKey(), entry.getValue());
482 }
483 }
484 if (this.fieldAliases != null) {
485 for (Map.Entry<String, String> entry : this.fieldAliases.entrySet()) {
486 String alias = entry.getValue();
487 String field = entry.getKey();
488 int idx = field.lastIndexOf('.');
489 if (idx != -1) {
490 String className = field.substring(0, idx);
491 Class<?> clazz = ClassUtils.forName(className, this.beanClassLoader);
492 String fieldName = field.substring(idx + 1);
493 xstream.aliasField(alias, clazz, fieldName);
494 }
495 else {
496 throw new IllegalArgumentException("Field name [" + field + "] does not contain '.'");
497 }
498 }
499 }
500 }
501 catch (ClassNotFoundException ex) {
502 throw new IllegalStateException("Failed to load specified alias class", ex);
503 }
504
505 if (this.useAttributeForTypes != null) {
506 for (Class<?> type : this.useAttributeForTypes) {
507 xstream.useAttributeFor(type);
508 }
509 }
510 if (this.useAttributeFor != null) {
511 for (Map.Entry<?, ?> entry : this.useAttributeFor.entrySet()) {
512 if (entry.getKey() instanceof String) {
513 if (entry.getValue() instanceof Class) {
514 xstream.useAttributeFor((String) entry.getKey(), (Class<?>) entry.getValue());
515 }
516 else {
517 throw new IllegalArgumentException(
518 "'useAttributesFor' takes Map<String, Class> when using a map key of type String");
519 }
520 }
521 else if (entry.getKey() instanceof Class) {
522 Class<?> key = (Class<?>) entry.getKey();
523 if (entry.getValue() instanceof String) {
524 xstream.useAttributeFor(key, (String) entry.getValue());
525 }
526 else if (entry.getValue() instanceof List) {
527 @SuppressWarnings("unchecked")
528 List<Object> listValue = (List<Object>) entry.getValue();
529 for (Object element : listValue) {
530 if (element instanceof String) {
531 xstream.useAttributeFor(key, (String) element);
532 }
533 }
534 }
535 else {
536 throw new IllegalArgumentException("'useAttributesFor' property takes either Map<Class, String> " +
537 "or Map<Class, List<String>> when using a map key of type Class");
538 }
539 }
540 else {
541 throw new IllegalArgumentException(
542 "'useAttributesFor' property takes either a map key of type String or Class");
543 }
544 }
545 }
546
547 if (this.implicitCollections != null) {
548 for (Map.Entry<Class<?>, String> entry : this.implicitCollections.entrySet()) {
549 String[] collectionFields = StringUtils.commaDelimitedListToStringArray(entry.getValue());
550 for (String collectionField : collectionFields) {
551 xstream.addImplicitCollection(entry.getKey(), collectionField);
552 }
553 }
554 }
555 if (this.omittedFields != null) {
556 for (Map.Entry<Class<?>, String> entry : this.omittedFields.entrySet()) {
557 String[] fields = StringUtils.commaDelimitedListToStringArray(entry.getValue());
558 for (String field : fields) {
559 xstream.omitField(entry.getKey(), field);
560 }
561 }
562 }
563
564 if (this.annotatedClasses != null) {
565 xstream.processAnnotations(this.annotatedClasses);
566 }
567 if (this.autodetectAnnotations) {
568 xstream.autodetectAnnotations(true);
569 }
570 }
571
572 private Map<String, Class<?>> toClassMap(Map<String, ?> map) throws ClassNotFoundException {
573 Map<String, Class<?>> result = new LinkedHashMap<String, Class<?>>(map.size());
574 for (Map.Entry<String, ?> entry : map.entrySet()) {
575 String key = entry.getKey();
576 Object value = entry.getValue();
577 Class<?> type;
578 if (value instanceof Class) {
579 type = (Class<?>) value;
580 }
581 else if (value instanceof String) {
582 String className = (String) value;
583 type = ClassUtils.forName(className, this.beanClassLoader);
584 }
585 else {
586 throw new IllegalArgumentException("Unknown value [" + value + "] - expected String or Class");
587 }
588 result.put(key, type);
589 }
590 return result;
591 }
592
593
594
595
596
597
598 protected void customizeXStream(XStream xstream) {
599 }
600
601
602
603
604
605
606
607 public final XStream getXStream() {
608 if (this.xstream == null) {
609 this.xstream = buildXStream();
610 }
611 return this.xstream;
612 }
613
614
615 @Override
616 public boolean supports(Class<?> clazz) {
617 if (ObjectUtils.isEmpty(this.supportedClasses)) {
618 return true;
619 }
620 else {
621 for (Class<?> supportedClass : this.supportedClasses) {
622 if (supportedClass.isAssignableFrom(clazz)) {
623 return true;
624 }
625 }
626 return false;
627 }
628 }
629
630
631
632
633 @Override
634 protected void marshalDomNode(Object graph, Node node) throws XmlMappingException {
635 HierarchicalStreamWriter streamWriter;
636 if (node instanceof Document) {
637 streamWriter = new DomWriter((Document) node, this.nameCoder);
638 }
639 else if (node instanceof Element) {
640 streamWriter = new DomWriter((Element) node, node.getOwnerDocument(), this.nameCoder);
641 }
642 else {
643 throw new IllegalArgumentException("DOMResult contains neither Document nor Element");
644 }
645 doMarshal(graph, streamWriter, null);
646 }
647
648 @Override
649 protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) throws XmlMappingException {
650 ContentHandler contentHandler = StaxUtils.createContentHandler(eventWriter);
651 LexicalHandler lexicalHandler = null;
652 if (contentHandler instanceof LexicalHandler) {
653 lexicalHandler = (LexicalHandler) contentHandler;
654 }
655 marshalSaxHandlers(graph, contentHandler, lexicalHandler);
656 }
657
658 @Override
659 protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException {
660 try {
661 doMarshal(graph, new StaxWriter(new QNameMap(), streamWriter, this.nameCoder), null);
662 }
663 catch (XMLStreamException ex) {
664 throw convertXStreamException(ex, true);
665 }
666 }
667
668 @Override
669 protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler)
670 throws XmlMappingException {
671
672 SaxWriter saxWriter = new SaxWriter(this.nameCoder);
673 saxWriter.setContentHandler(contentHandler);
674 doMarshal(graph, saxWriter, null);
675 }
676
677 @Override
678 public void marshalOutputStream(Object graph, OutputStream outputStream) throws XmlMappingException, IOException {
679 marshalOutputStream(graph, outputStream, null);
680 }
681
682 public void marshalOutputStream(Object graph, OutputStream outputStream, DataHolder dataHolder)
683 throws XmlMappingException, IOException {
684
685 if (this.streamDriver != null) {
686 doMarshal(graph, this.streamDriver.createWriter(outputStream), dataHolder);
687 }
688 else {
689 marshalWriter(graph, new OutputStreamWriter(outputStream, this.encoding), dataHolder);
690 }
691 }
692
693 @Override
694 public void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException {
695 marshalWriter(graph, writer, null);
696 }
697
698 public void marshalWriter(Object graph, Writer writer, DataHolder dataHolder)
699 throws XmlMappingException, IOException {
700
701 if (this.streamDriver != null) {
702 doMarshal(graph, this.streamDriver.createWriter(writer), dataHolder);
703 }
704 else {
705 doMarshal(graph, new CompactWriter(writer), dataHolder);
706 }
707 }
708
709
710
711
712
713 private void doMarshal(Object graph, HierarchicalStreamWriter streamWriter, DataHolder dataHolder) {
714 try {
715 getXStream().marshal(graph, streamWriter, dataHolder);
716 }
717 catch (Exception ex) {
718 throw convertXStreamException(ex, true);
719 }
720 finally {
721 try {
722 streamWriter.flush();
723 }
724 catch (Exception ex) {
725 logger.debug("Could not flush HierarchicalStreamWriter", ex);
726 }
727 }
728 }
729
730
731
732
733 @Override
734 protected Object unmarshalStreamSource(StreamSource streamSource) throws XmlMappingException, IOException {
735 if (streamSource.getInputStream() != null) {
736 return unmarshalInputStream(streamSource.getInputStream());
737 }
738 else if (streamSource.getReader() != null) {
739 return unmarshalReader(streamSource.getReader());
740 }
741 else {
742 throw new IllegalArgumentException("StreamSource contains neither InputStream nor Reader");
743 }
744 }
745
746 @Override
747 protected Object unmarshalDomNode(Node node) throws XmlMappingException {
748 HierarchicalStreamReader streamReader;
749 if (node instanceof Document) {
750 streamReader = new DomReader((Document) node, this.nameCoder);
751 }
752 else if (node instanceof Element) {
753 streamReader = new DomReader((Element) node, this.nameCoder);
754 }
755 else {
756 throw new IllegalArgumentException("DOMSource contains neither Document nor Element");
757 }
758 return doUnmarshal(streamReader, null);
759 }
760
761 @Override
762 protected Object unmarshalXmlEventReader(XMLEventReader eventReader) throws XmlMappingException {
763 try {
764 XMLStreamReader streamReader = StaxUtils.createEventStreamReader(eventReader);
765 return unmarshalXmlStreamReader(streamReader);
766 }
767 catch (XMLStreamException ex) {
768 throw convertXStreamException(ex, false);
769 }
770 }
771
772 @Override
773 protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) throws XmlMappingException {
774 return doUnmarshal(new StaxReader(new QNameMap(), streamReader, this.nameCoder), null);
775 }
776
777 @Override
778 protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
779 throws XmlMappingException, IOException {
780
781 throw new UnsupportedOperationException(
782 "XStreamMarshaller does not support unmarshalling using SAX XMLReaders");
783 }
784
785 @Override
786 public Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException {
787 return unmarshalInputStream(inputStream, null);
788 }
789
790 public Object unmarshalInputStream(InputStream inputStream, DataHolder dataHolder) throws XmlMappingException, IOException {
791 if (this.streamDriver != null) {
792 return doUnmarshal(this.streamDriver.createReader(inputStream), dataHolder);
793 }
794 else {
795 return unmarshalReader(new InputStreamReader(inputStream, this.encoding), dataHolder);
796 }
797 }
798
799 @Override
800 public Object unmarshalReader(Reader reader) throws XmlMappingException, IOException {
801 return unmarshalReader(reader, null);
802 }
803
804 public Object unmarshalReader(Reader reader, DataHolder dataHolder) throws XmlMappingException, IOException {
805 return doUnmarshal(getDefaultDriver().createReader(reader), dataHolder);
806 }
807
808
809
810
811
812 private Object doUnmarshal(HierarchicalStreamReader streamReader, DataHolder dataHolder) {
813 try {
814 return getXStream().unmarshal(streamReader, null, dataHolder);
815 }
816 catch (Exception ex) {
817 throw convertXStreamException(ex, false);
818 }
819 }
820
821
822
823
824
825
826
827
828
829
830
831
832 protected XmlMappingException convertXStreamException(Exception ex, boolean marshalling) {
833 if (ex instanceof StreamException || ex instanceof CannotResolveClassException ||
834 ex instanceof ConversionException) {
835 if (marshalling) {
836 return new MarshallingFailureException("XStream marshalling exception", ex);
837 }
838 else {
839 return new UnmarshallingFailureException("XStream unmarshalling exception", ex);
840 }
841 }
842 else {
843
844 return new UncategorizedMappingException("Unknown XStream exception", ex);
845 }
846 }
847
848 }