View Javadoc

1   package org.apache.xmlrpc;
2   
3   /*
4    * The Apache Software License, Version 1.1
5    *
6    *
7    * Copyright (c) 2001 The Apache Software Foundation.  All rights
8    * reserved.
9    *
10   * Redistribution and use in source and binary forms, with or without
11   * modification, are permitted provided that the following conditions
12   * are met:
13   *
14   * 1. Redistributions of source code must retain the above copyright
15   *    notice, this list of conditions and the following disclaimer.
16   *
17   * 2. Redistributions in binary form must reproduce the above copyright
18   *    notice, this list of conditions and the following disclaimer in
19   *    the documentation and/or other materials provided with the
20   *    distribution.
21   *
22   * 3. The end-user documentation included with the redistribution,
23   *    if any, must include the following acknowledgment:
24   *       "This product includes software developed by the
25   *        Apache Software Foundation (http://www.apache.org/)."
26   *    Alternately, this acknowledgment may appear in the software itself,
27   *    if and wherever such third-party acknowledgments normally appear.
28   *
29   * 4. The names "XML-RPC" and "Apache Software Foundation" must
30   *    not be used to endorse or promote products derived from this
31   *    software without prior written permission. For written
32   *    permission, please contact apache@apache.org.
33   *
34   * 5. Products derived from this software may not be called "Apache",
35   *    nor may "Apache" appear in their name, without prior written
36   *    permission of the Apache Software Foundation.
37   *
38   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
39   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
40   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
41   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
42   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
43   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
44   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
45   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
46   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
47   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
48   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
49   * SUCH DAMAGE.
50   * ====================================================================
51   *
52   * This software consists of voluntary contributions made by many
53   * individuals on behalf of the Apache Software Foundation.  For more
54   * information on the Apache Software Foundation, please see
55   * <http://www.apache.org/>.
56   */
57  
58  import java.io.IOException;
59  import java.io.OutputStream;
60  import java.io.OutputStreamWriter;
61  import java.io.UnsupportedEncodingException;
62  import java.util.Date;
63  import java.util.Enumeration;
64  import java.util.Hashtable;
65  import java.util.Properties;
66  import java.util.Vector;
67  
68  import org.apache.xmlrpc.util.DateTool;
69  
70  /***
71   * A quick and dirty XML writer.  If you feed it a
72   * <code>ByteArrayInputStream</code>, it may be necessary to call
73   * <code>writer.flush()</code> before calling
74   * <code>buffer.toByteArray()</code> to get the data written to
75   * your byte buffer.
76   *
77   * @author <a href="mailto:hannes@apache.org">Hannes Wallnoefer</a>
78   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
79   */
80  class XmlWriter extends OutputStreamWriter {
81      // Various XML pieces.
82      protected static final String PROLOG_START =
83          "<?xml version=\"1.0\" encoding=\"";
84      protected static final String PROLOG_END = "\"?>";
85      protected static final String CLOSING_TAG_START = "</";
86      protected static final String SINGLE_TAG_END = "/>";
87      protected static final String LESS_THAN_ENTITY = "&lt;";
88      protected static final String GREATER_THAN_ENTITY = "&gt;";
89      protected static final String AMPERSAND_ENTITY = "&amp;";
90  
91      /***
92       * Java's name for the the ISO8859_1 encoding.
93       */
94      protected static final String ISO8859_1 = "ISO8859_1";
95  
96      /***
97       * Java's name for the the UTF8 encoding.
98       */
99      protected static final String UTF8 = "UTF8";
100 
101     /***
102      * Mapping between Java encoding names and "real" names used in
103      * XML prolog.
104      */
105     private static Properties encodings = new Properties();
106 
107     static {
108         encodings.put(UTF8, "UTF-8");
109         encodings.put(ISO8859_1, "ISO-8859-1");
110     }
111 
112     /***
113      * Thread-safe wrapper for the <code>DateFormat</code> object used
114      * to parse date/time values.
115      */
116     private static DateTool dateTool = new DateTool();
117 
118     /***
119      * Creates a new instance.
120      *
121      * @param out The stream to write output to.
122      * @param enc The encoding to using for outputing XML.
123      * @throws UnsupportedEncodingException Encoding unrecognized.
124      * @throws IOException Problem writing.
125      */
126     public XmlWriter(OutputStream out, String enc)
127         throws UnsupportedEncodingException, IOException {
128         // Super-class wants the Java form of the encoding.
129         super(out, enc);
130 
131         // Add the XML prolog (including the encoding in XML form).
132         write(PROLOG_START);
133         write(canonicalizeEncoding(enc));
134         write(PROLOG_END);
135     }
136 
137     /***
138      * Tranforms a Java encoding to the canonical XML form (if a
139      * mapping is available).
140      *
141      * @param javaEncoding The name of the encoding as known by Java.
142      * @return The XML encoding (if a mapping is available);
143      * otherwise, the encoding as provided.
144      */
145     protected static String canonicalizeEncoding(String javaEncoding) {
146         return encodings.getProperty(javaEncoding, javaEncoding);
147     }
148 
149     /***
150      * Writes the XML representation of a supported Java object type.
151      *
152      * @param obj The <code>Object</code> to write.
153      * @exception XmlRpcException Unsupported character data found.
154      * @exception IOException Problem writing data.
155      * @throws IllegalArgumentException If a <code>null</code>
156      * parameter is passed to this method (not supported by the <a
157      * href="http://xml-rpc.com/spec">XML-RPC specification</a>).
158      */
159     public void writeObject(Object obj) throws XmlRpcException, IOException {
160         startElement("value");
161         if (obj == null) {
162             throw new IllegalArgumentException("null values not supported by XML-RPC");
163         } else if (obj instanceof String) {
164             chardata(obj.toString());
165         } else if (obj instanceof Integer) {
166             startElement("int");
167             write(obj.toString());
168             endElement("int");
169         } else if (obj instanceof Boolean) {
170             startElement("boolean");
171             write(((Boolean)obj).booleanValue() ? "1" : "0");
172             endElement("boolean");
173         } else if (obj instanceof Double || obj instanceof Float) {
174             startElement("double");
175             write(obj.toString());
176             endElement("double");
177         } else if (obj instanceof Date) {
178             startElement("dateTime.iso8601");
179             Date d = (Date)obj;
180             write(dateTool.format(d));
181             endElement("dateTime.iso8601");
182         } else if (obj instanceof byte[]) {
183             startElement("base64");
184             this.write(Base64.encode((byte[])obj));
185             endElement("base64");
186         } else if (obj instanceof Object[]) {
187             startElement("array");
188             startElement("data");
189             Object[] array = (Object[])obj;
190             for (int i = 0; i < array.length; i++) {
191                 writeObject(array[i]);
192             }
193             endElement("data");
194             endElement("array");
195         } else if (obj instanceof Vector) {
196             startElement("array");
197             startElement("data");
198             Vector array = (Vector)obj;
199             int size = array.size();
200             for (int i = 0; i < size; i++) {
201                 writeObject(array.elementAt(i));
202             }
203             endElement("data");
204             endElement("array");
205         } else if (obj instanceof Hashtable) {
206             startElement("struct");
207             Hashtable struct = (Hashtable)obj;
208             for (Enumeration e = struct.keys(); e.hasMoreElements();) {
209                 String key = (String)e.nextElement();
210                 Object value = struct.get(key);
211                 startElement("member");
212                 startElement("name");
213                 chardata(key);
214                 endElement("name");
215                 writeObject(value);
216                 endElement("member");
217             }
218             endElement("struct");
219         } else {
220             throw new RuntimeException(
221                 "unsupported Java type: " + obj.getClass());
222         }
223         endElement("value");
224     }
225 
226     /***
227      * This is used to write out the Base64 output...
228      */
229     protected void write(byte[] byteData) throws IOException {
230         for (int i = 0; i < byteData.length; i++) {
231             write(byteData[i]);
232         }
233     }
234 
235     /***
236      *
237      * @param elem
238      * @throws IOException
239      */
240     protected void startElement(String elem) throws IOException {
241         write('<');
242         write(elem);
243         write('>');
244     }
245 
246     /***
247      *
248      * @param elem
249      * @throws IOException
250      */
251     protected void endElement(String elem) throws IOException {
252         write(CLOSING_TAG_START);
253         write(elem);
254         write('>');
255     }
256 
257     /***
258      *
259      * @param elem
260      * @throws IOException
261      */
262     protected void emptyElement(String elem) throws IOException {
263         write('<');
264         write(elem);
265         write(SINGLE_TAG_END);
266     }
267 
268     /***
269      * Writes text as <code>PCDATA</code>.
270      *
271      * @param text The data to write.
272      * @exception XmlRpcException Unsupported character data found.
273      * @exception IOException Problem writing data.
274      */
275     protected void chardata(String text) throws XmlRpcException, IOException {
276         int l = text.length();
277         for (int i = 0; i < l; i++) {
278             char c = text.charAt(i);
279             switch (c) {
280                 case '\t' :
281                 case '\r' :
282                 case '\n' :
283                     write(c);
284                     break;
285                 case '<' :
286                     write(LESS_THAN_ENTITY);
287                     break;
288                 case '>' :
289                     write(GREATER_THAN_ENTITY);
290                     break;
291                 case '&' :
292                     write(AMPERSAND_ENTITY);
293                     break;
294                 default :
295                     if (c < 0x20) {
296                         // Though the XML-RPC spec allows any ASCII
297                         // characters except '<' and '&', the XML spec
298                         // does not allow this range of characters,
299                         // resulting in a parse error from most XML
300                         // parsers.
301                         throw new XmlRpcException(
302                             0,
303                             "Invalid character data "
304                                 + "corresponding to XML entity &#"
305                                 + String.valueOf((int)c)
306                                 + ';');
307                     } else {
308                         write(c);
309                     }
310             }
311         }
312     }
313 }