001: /*
002: * Copyright 2006 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: * http://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:
017: package org.springframework.oxm.xstream;
018:
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.io.InputStreamReader;
022: import java.io.OutputStream;
023: import java.io.OutputStreamWriter;
024: import java.io.Reader;
025: import java.io.Writer;
026: import java.util.Iterator;
027: import java.util.Map;
028: import javax.xml.stream.XMLEventReader;
029: import javax.xml.stream.XMLEventWriter;
030: import javax.xml.stream.XMLStreamException;
031: import javax.xml.stream.XMLStreamReader;
032: import javax.xml.stream.XMLStreamWriter;
033:
034: import com.thoughtworks.xstream.XStream;
035: import com.thoughtworks.xstream.converters.Converter;
036: import com.thoughtworks.xstream.converters.ConverterMatcher;
037: import com.thoughtworks.xstream.converters.SingleValueConverter;
038: import com.thoughtworks.xstream.io.HierarchicalStreamReader;
039: import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
040: import com.thoughtworks.xstream.io.xml.CompactWriter;
041: import com.thoughtworks.xstream.io.xml.DomReader;
042: import com.thoughtworks.xstream.io.xml.DomWriter;
043: import com.thoughtworks.xstream.io.xml.QNameMap;
044: import com.thoughtworks.xstream.io.xml.SaxWriter;
045: import com.thoughtworks.xstream.io.xml.StaxReader;
046: import com.thoughtworks.xstream.io.xml.StaxWriter;
047: import com.thoughtworks.xstream.io.xml.XppReader;
048: import org.springframework.beans.propertyeditors.ClassEditor;
049: import org.springframework.oxm.AbstractMarshaller;
050: import org.springframework.oxm.XmlMappingException;
051: import org.springframework.xml.stream.StaxEventContentHandler;
052: import org.springframework.xml.stream.XmlEventStreamReader;
053: import org.w3c.dom.Document;
054: import org.w3c.dom.Element;
055: import org.w3c.dom.Node;
056: import org.xml.sax.ContentHandler;
057: import org.xml.sax.InputSource;
058: import org.xml.sax.XMLReader;
059: import org.xml.sax.ext.LexicalHandler;
060:
061: /**
062: * Implementation of the <code>Marshaller</code> interface for XStream. By default, XStream does not require any further
063: * configuration, though class aliases can be used to have more control over the behavior of XStream.
064: * <p/>
065: * Due to XStream's API, it is required to set the encoding used for writing to outputstreams. It defaults to
066: * <code>UTF-8</code>.
067: * <p/>
068: * <b>Note</b> that XStream is an XML serialization library, not a data binding library. Therefore, it has limited
069: * namespace support. As such, it is rather unsuitable for usage within Web services.
070: *
071: * @author Peter Meijer
072: * @author Arjen Poutsma
073: * @see #setEncoding(String)
074: * @see #DEFAULT_ENCODING
075: * @see #setAliases(Map)
076: * @see #setConverters(ConverterMatcher[])
077: * @since 1.0.0
078: */
079: public class XStreamMarshaller extends AbstractMarshaller {
080:
081: /** The default encoding used for stream access. */
082: public static final String DEFAULT_ENCODING = "UTF-8";
083:
084: private XStream xstream = new XStream();
085:
086: private String encoding;
087:
088: /**
089: * Returns the encoding to be used for stream access. If this property is not set, the default encoding is used.
090: *
091: * @see #DEFAULT_ENCODING
092: */
093: public String getEncoding() {
094: return encoding != null ? encoding : DEFAULT_ENCODING;
095: }
096:
097: /**
098: * Sets the encoding to be used for stream access. If this property is not set, the default encoding is used.
099: *
100: * @see #DEFAULT_ENCODING
101: */
102: public void setEncoding(String encoding) {
103: this .encoding = encoding;
104: }
105:
106: /**
107: * Sets the XStream mode.
108: *
109: * @see XStream#XPATH_REFERENCES
110: * @see XStream#ID_REFERENCES
111: * @see XStream#NO_REFERENCES
112: */
113: public void setMode(int mode) {
114: xstream.setMode(mode);
115: }
116:
117: /**
118: * Sets the <code>Converters</code> or <code>SingleValueConverters</code> to be registered with the
119: * <code>XStream</code> instance.
120: *
121: * @see Converter
122: * @see SingleValueConverter
123: */
124: public void setConverters(ConverterMatcher[] converters) {
125: for (int i = 0; i < converters.length; i++) {
126: if (converters[i] instanceof Converter) {
127: xstream.registerConverter((Converter) converters[i], i);
128: } else if (converters[i] instanceof SingleValueConverter) {
129: xstream.registerConverter(
130: (SingleValueConverter) converters[i], i);
131: } else {
132: throw new IllegalArgumentException(
133: "Invalid ConverterMatcher [" + converters[i]
134: + "]");
135: }
136: }
137: }
138:
139: /**
140: * Set a alias/type map, consisting of string aliases mapped to <code>Class</code> instances (or Strings to be
141: * converted to <code>Class</code> instances).
142: *
143: * @see org.springframework.beans.propertyeditors.ClassEditor
144: */
145: public void setAliases(Map aliases) {
146: for (Iterator iterator = aliases.entrySet().iterator(); iterator
147: .hasNext();) {
148: Map.Entry entry = (Map.Entry) iterator.next();
149: // Check whether we need to convert from String to Class.
150: Class type = null;
151: if (entry.getValue() instanceof Class) {
152: type = (Class) entry.getValue();
153: } else {
154: ClassEditor editor = new ClassEditor();
155: editor.setAsText(String.valueOf(entry.getValue()));
156: type = (Class) editor.getValue();
157: }
158: addAlias((String) entry.getKey(), type);
159: }
160: }
161:
162: /**
163: * Adds an alias for the given type.
164: *
165: * @param name alias to be used for the type
166: * @param type the type to be aliased
167: */
168: public void addAlias(String name, Class type) {
169: xstream.alias(name, type);
170: }
171:
172: public boolean supports(Class clazz) {
173: return true;
174: }
175:
176: /**
177: * Convert the given XStream exception to an appropriate exception from the <code>org.springframework.oxm</code>
178: * hierarchy.
179: * <p/>
180: * The default implementation delegates to <code>XStreamUtils</code>. Can be overridden in subclasses.
181: *
182: * @param ex exception that occured
183: * @param marshalling indicates whether the exception occurs during marshalling (<code>true</code>), or
184: * unmarshalling (<code>false</code>)
185: * @return the corresponding <code>XmlMappingException</code> instance
186: * @see XStreamUtils#convertXStreamException(Exception,boolean)
187: */
188: public XmlMappingException convertXStreamException(Exception ex,
189: boolean marshalling) {
190: return XStreamUtils.convertXStreamException(ex, marshalling);
191: }
192:
193: /**
194: * Marshals the given graph to the given XStream HierarchicalStreamWriter. Converts exceptions using
195: * <code>convertXStreamException</code>.
196: */
197: private void marshal(Object graph,
198: HierarchicalStreamWriter streamWriter) {
199: try {
200: xstream.marshal(graph, streamWriter);
201: } catch (Exception ex) {
202: throw convertXStreamException(ex, true);
203: }
204: }
205:
206: protected void marshalDomNode(Object graph, Node node)
207: throws XmlMappingException {
208: HierarchicalStreamWriter streamWriter = null;
209: if (node instanceof Document) {
210: streamWriter = new DomWriter((Document) node);
211: } else if (node instanceof Element) {
212: streamWriter = new DomWriter((Element) node);
213: } else {
214: throw new IllegalArgumentException(
215: "DOMResult contains neither Document nor Element");
216: }
217: marshal(graph, streamWriter);
218: }
219:
220: protected void marshalXmlEventWriter(Object graph,
221: XMLEventWriter eventWriter) throws XmlMappingException {
222: ContentHandler contentHandler = new StaxEventContentHandler(
223: eventWriter);
224: marshalSaxHandlers(graph, contentHandler, null);
225: }
226:
227: protected void marshalXmlStreamWriter(Object graph,
228: XMLStreamWriter streamWriter) throws XmlMappingException {
229: try {
230: marshal(graph, new StaxWriter(new QNameMap(), streamWriter));
231: } catch (XMLStreamException ex) {
232: throw convertXStreamException(ex, true);
233: }
234: }
235:
236: protected void marshalOutputStream(Object graph,
237: OutputStream outputStream) throws XmlMappingException,
238: IOException {
239: marshalWriter(graph, new OutputStreamWriter(outputStream,
240: getEncoding()));
241: }
242:
243: protected void marshalSaxHandlers(Object graph,
244: ContentHandler contentHandler, LexicalHandler lexicalHandler)
245: throws XmlMappingException {
246: SaxWriter saxWriter = new SaxWriter();
247: saxWriter.setContentHandler(contentHandler);
248: marshal(graph, saxWriter);
249: }
250:
251: protected void marshalWriter(Object graph, Writer writer)
252: throws XmlMappingException, IOException {
253: marshal(graph, new CompactWriter(writer));
254: }
255:
256: private Object unmarshal(HierarchicalStreamReader streamReader) {
257: try {
258: return xstream.unmarshal(streamReader);
259: } catch (Exception ex) {
260: throw convertXStreamException(ex, false);
261: }
262: }
263:
264: protected Object unmarshalDomNode(Node node)
265: throws XmlMappingException {
266: HierarchicalStreamReader streamReader = null;
267: if (node instanceof Document) {
268: streamReader = new DomReader((Document) node);
269: } else if (node instanceof Element) {
270: streamReader = new DomReader((Element) node);
271: } else {
272: throw new IllegalArgumentException(
273: "DOMSource contains neither Document nor Element");
274: }
275: return unmarshal(streamReader);
276: }
277:
278: protected Object unmarshalXmlEventReader(XMLEventReader eventReader)
279: throws XmlMappingException {
280: try {
281: XMLStreamReader streamReader = new XmlEventStreamReader(
282: eventReader);
283: return unmarshalXmlStreamReader(streamReader);
284: } catch (XMLStreamException ex) {
285: throw convertXStreamException(ex, false);
286: }
287: }
288:
289: protected Object unmarshalXmlStreamReader(
290: XMLStreamReader streamReader) throws XmlMappingException {
291: return unmarshal(new StaxReader(new QNameMap(), streamReader));
292: }
293:
294: protected Object unmarshalInputStream(InputStream inputStream)
295: throws XmlMappingException, IOException {
296: return unmarshalReader(new InputStreamReader(inputStream,
297: getEncoding()));
298: }
299:
300: protected Object unmarshalReader(Reader reader)
301: throws XmlMappingException, IOException {
302: return unmarshal(new XppReader(reader));
303: }
304:
305: protected Object unmarshalSaxReader(XMLReader xmlReader,
306: InputSource inputSource) throws XmlMappingException,
307: IOException {
308: throw new UnsupportedOperationException(
309: "XStreamMarshaller does not support unmarshalling using SAX XMLReaders");
310: }
311: }
|