KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > xmlrpc > XmlRpc


1 /*
2  * Copyright 1999,2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17
18 package org.apache.xmlrpc;
19
20 import java.io.InputStream JavaDoc;
21 import java.io.InputStreamReader JavaDoc;
22 import java.util.Hashtable JavaDoc;
23 import java.util.Stack JavaDoc;
24 import java.util.Vector JavaDoc;
25 import org.xml.sax.AttributeList JavaDoc;
26 import org.xml.sax.HandlerBase JavaDoc;
27 import org.xml.sax.InputSource JavaDoc;
28 import org.xml.sax.Parser JavaDoc;
29 import org.xml.sax.SAXException JavaDoc;
30 import org.xml.sax.SAXParseException JavaDoc;
31 import uk.co.wilson.xml.MinML;
32
33 /**
34  * This abstract base class provides basic capabilities for XML-RPC,
35  * like parsing of parameters or encoding Java objects into XML-RPC
36  * format. Any XML parser with a <a
37  * HREF="http://www.megginson.com/SAX/">SAX</a> interface can be used.
38  *
39  * <p>XmlRpcServer and XmlRpcClient are the classes that actually
40  * implement an XML-RPC server and client.
41  *
42  * @see org.apache.xmlrpc.XmlRpcServer
43  * @see org.apache.xmlrpc.XmlRpcClient
44  *
45  * @author <a HREF="mailto:hannes@apache.org">Hannes Wallnoefer</a>
46  * @author Daniel L. Rall
47  * @author <a HREF="mailto:andrew@kungfoocoder.org">Andrew Evers</a>
48  * @version $Id: XmlRpc.java,v 1.42 2005/05/16 21:23:21 dlr Exp $
49  */

50 public abstract class XmlRpc extends HandlerBase JavaDoc
51 {
52     /**
53      * The version string used in HTTP communication.
54      */

55     // FIXME: Use Ant <filter> to pre-process version number into a
56
// class-loaded .properties file at build time. Use here when
57
// available, and otherwise provide no version string.
58
public static final String JavaDoc version = "Apache XML-RPC 2.0";
59
60     /**
61      * The default parser to use (MinML).
62      */

63     private static final String JavaDoc DEFAULT_PARSER = MinML.class.getName();
64
65     /**
66      * The maximum number of threads which can be used concurrently.
67      */

68     private static int maxThreads = 100;
69
70     String JavaDoc methodName;
71
72     /**
73      * The class name of SAX parser to use.
74      */

75     private static Class JavaDoc parserClass;
76     private static Hashtable JavaDoc saxDrivers = new Hashtable JavaDoc (8);
77
78     static
79     {
80         // A mapping of short identifiers to the fully qualified class
81
// names of common SAX parsers. If more mappings are added
82
// here, increase the size of the saxDrivers Map used to store
83
// them.
84
saxDrivers.put("xerces", "org.apache.xerces.parsers.SAXParser");
85         saxDrivers.put("xp", "com.jclark.xml.sax.Driver");
86         saxDrivers.put("ibm1", "com.ibm.xml.parser.SAXDriver");
87         saxDrivers.put("ibm2", "com.ibm.xml.parsers.SAXParser");
88         saxDrivers.put("aelfred", "com.microstar.xml.SAXDriver");
89         saxDrivers.put("oracle1", "oracle.xml.parser.XMLParser");
90         saxDrivers.put("oracle2", "oracle.xml.parser.v2.SAXParser");
91         saxDrivers.put("openxml", "org.openxml.parser.XMLSAXParser");
92     }
93
94     // the stack we're parsing our values into.
95
Stack JavaDoc values;
96     Value currentValue;
97
98     /**
99      * Used to collect character data (<code>CDATA</code>) of
100      * parameter values.
101      */

102     StringBuffer JavaDoc cdata;
103     boolean readCdata;
104
105     // XML RPC parameter types used for dataMode
106
static final int STRING = 0;
107     static final int INTEGER = 1;
108     static final int BOOLEAN = 2;
109     static final int DOUBLE = 3;
110     static final int DATE = 4;
111     static final int BASE64 = 5;
112     static final int STRUCT = 6;
113     static final int ARRAY = 7;
114
115     // Error level + message
116
int errorLevel;
117     String JavaDoc errorMsg;
118
119     static final int NONE = 0;
120     static final int RECOVERABLE = 1;
121     static final int FATAL = 2;
122
123     /**
124      * Wheter to use HTTP Keep-Alive headers.
125      */

126     static boolean keepalive = false;
127
128     /**
129      * Whether to log debugging output.
130      */

131     public static boolean debug = false;
132
133     /**
134      * The list of valid XML elements used for RPC.
135      */

136     final static String JavaDoc types[] =
137     {
138         "String",
139         "Integer",
140         "Boolean",
141         "Double",
142         "Date",
143         "Base64",
144         "Struct",
145         "Array"
146     };
147
148     /**
149      * Java's name for the encoding we're using. Defaults to
150      * <code>UTF8</code> (of which <code>ISO8859_1</code> is a
151      * subset).
152      */

153     static String JavaDoc encoding = XmlWriter.UTF8;
154
155     /**
156      * Java's name for the input encoding we're using. Defaults to
157      * <code>null</code>, signifying the platform default. This may
158      * need to be overridden on platforms where the default encoding
159      * is not compatible with ASCII (eg. EBCDIC) but the network is
160      * still ASCII-like.
161      */

162     static String JavaDoc defaultInputEncoding = null;
163  
164     private TypeFactory typeFactory;
165     private String JavaDoc inputEncoding;
166
167     /**
168      * Creates a new instance with the {@link
169      * org.apache.xmlrpc.TypeFactory} set to an instance of the class
170      * named by the <code>org.apache.xmlrpc.TypeFactory</code> System
171      * property. If property not set or class is unavailable, uses
172      * the default of {@link org.apache.xmlrpc.TypeFactory}.
173      */

174     protected XmlRpc()
175     {
176         String JavaDoc typeFactoryName = null;
177         try
178         {
179             typeFactoryName = System.getProperty(TypeFactory.class.getName());
180         }
181         catch (SecurityException JavaDoc e)
182         {
183             // An unsigned applet may not access system properties.
184
// No-op means we use the default TypeFactory instead.
185
if (debug)
186             {
187                 System.out.println("Unable to determine the value of the " +
188                                    "system property '" +
189                                    TypeFactory.class.getName() + "': " +
190                                    e.getMessage());
191             }
192         }
193         this.typeFactory = createTypeFactory(typeFactoryName);
194         this.inputEncoding = defaultInputEncoding;
195     }
196
197     /**
198      * Creates a new instance with the specified {@link
199      * org.apache.xmlrpc.TypeFactory}.
200      *
201      * @param typeFactoryName The fully qualified class name of the
202      * {@link org.apache.xmlrpc.TypeFactory} implementation to use.
203      */

204     protected XmlRpc(String JavaDoc typeFactoryName)
205     {
206         this.typeFactory = createTypeFactory(typeFactoryName);
207     }
208
209     /**
210      * Creates a new instance of the specified {@link
211      * org.apache.xmlrpc.TypeFactory}.
212      *
213      * @param className The fully qualified class name of the
214      * implementation to use.
215      * @return The new type mapping.
216      */

217     private TypeFactory createTypeFactory(String JavaDoc className)
218     {
219         Class JavaDoc c = null;
220         if (className != null && className.length() > 0)
221         {
222             try
223             {
224                 c = Class.forName(className);
225             }
226             catch (ClassNotFoundException JavaDoc e)
227             {
228                 System.err.println("Error loading TypeFactory '" +
229                                    "' " + c.getName() +
230                                    "': Using the default instead: " +
231                                    e.getMessage());
232             }
233         }
234
235         // If we're using the default, provide it immediately.
236
if (c == null || DefaultTypeFactory.class.equals(c))
237         {
238             return new DefaultTypeFactory();
239         }
240
241         try
242         {
243             return (TypeFactory) c.newInstance();
244         }
245         catch (Exception JavaDoc e)
246         {
247             System.err.println("Unable to create configured TypeFactory '" +
248                                c.getName() + "': " + e.getMessage() +
249                                ": Falling back to default");
250             if (debug)
251             {
252                 e.printStackTrace();
253             }
254             return new DefaultTypeFactory();
255         }
256     }
257
258     /**
259      * Set the SAX Parser to be used. The argument can either be the
260      * full class name or a user friendly shortcut if the parser is
261      * known to this class. The parsers that can currently be set by
262      * shortcut are listed in the main documentation page. If you are
263      * using another parser please send me the name of the SAX driver
264      * and I'll include it in a future release. If setDriver() is
265      * never called then the System property "sax.driver" is
266      * consulted. If that is not defined the driver defaults to
267      * OpenXML.
268      */

269     public static void setDriver(String JavaDoc driver) throws ClassNotFoundException JavaDoc
270     {
271         String JavaDoc parserClassName = null;
272         try
273         {
274             parserClassName = (String JavaDoc) saxDrivers.get(driver);
275             if (parserClassName == null)
276             {
277                 // Identifier lookup failed, assuming we were provided
278
// with the fully qualified class name.
279
parserClassName = driver;
280             }
281             parserClass = Class.forName(parserClassName);
282         }
283         catch (ClassNotFoundException JavaDoc x)
284         {
285             throw new ClassNotFoundException JavaDoc ("SAX driver not found: "
286                     + parserClassName);
287         }
288     }
289
290     /**
291      * Set the SAX Parser to be used by directly passing the Class object.
292      */

293     public static void setDriver(Class JavaDoc driver)
294     {
295         parserClass = driver;
296     }
297
298     /**
299      * Set the encoding of the XML.
300      *
301      * @param enc The Java name of the encoding.
302      */

303     public static void setEncoding(String JavaDoc enc)
304     {
305         encoding = enc;
306     }
307
308     /**
309      * Return the encoding, transforming to the canonical name if
310      * possible.
311      *
312      * @see org.apache.xmlrpc.XmlWriter#canonicalizeEncoding(String)
313      */

314     public String JavaDoc getEncoding()
315     {
316         return XmlWriter.canonicalizeEncoding(encoding);
317     }
318
319     /** Set the default input encoding of the XML.
320      * This is used only if set.
321      *
322      * @param enc The Java name of the encoding.
323      * @see #setInputEncoding(String)
324      */

325     public static void setDefaultInputEncoding(String JavaDoc enc)
326     {
327         defaultInputEncoding = enc;
328     }
329
330     /**
331      * Return the default input encoding. This may be null.
332      * This is always a Java encoding name, it is not transformed.
333      *
334      * @return the Java encoding name to use, if set, otherwise null.
335      * @see #getInputEncoding()
336      */

337     public static String JavaDoc getDefaultInputEncoding()
338     {
339         return defaultInputEncoding;
340     }
341
342     /**
343      * Set the input encoding for this XmlRpc instance. This can be
344      * used when the XMLRPC response does not contain the proper
345      * encoding information in the XML declaration.
346      *
347      * @param enc The Java name of the encoding.
348      */

349     public void setInputEncoding(String JavaDoc enc)
350     {
351         inputEncoding = enc;
352     }
353
354     /**
355      * Get the input encoding for this XmlRpc instance. This is a Java
356      * encoding name.
357      *
358      * @return The Java encoding name to use. <code>null</code> if not set.
359      */

360     public String JavaDoc getInputEncoding()
361     {
362         return inputEncoding;
363     }
364
365     /**
366      * Gets the maximum number of threads used at any given moment.
367      */

368     public static int getMaxThreads()
369     {
370         return maxThreads;
371     }
372
373     /**
374      * Sets the maximum number of threads used at any given moment.
375      */

376     public static void setMaxThreads(int maxThreads)
377     {
378         XmlRpc.maxThreads = maxThreads;
379     }
380
381     /**
382      * Switch debugging output on/off.
383      */

384     public static void setDebug(boolean val)
385     {
386         debug = val;
387     }
388
389     /**
390      * Switch HTTP keepalive on/off.
391      */

392     public static void setKeepAlive(boolean val)
393     {
394         keepalive = val;
395     }
396
397     /**
398      * get current HTTP keepalive mode.
399      */

400     public static boolean getKeepAlive()
401     {
402         return keepalive;
403     }
404
405     /**
406      * Parse the input stream. For each root level object, method
407      * <code>objectParsed</code> is called.
408      */

409     synchronized void parse(InputStream JavaDoc is) throws Exception JavaDoc
410     {
411         // reset values (XmlRpc objects are reusable)
412
errorLevel = NONE;
413         errorMsg = null;
414         values = new Stack JavaDoc();
415         if (cdata == null)
416         {
417             cdata = new StringBuffer JavaDoc(128);
418         }
419         else
420         {
421             cdata.setLength(0);
422         }
423         readCdata = false;
424         currentValue = null;
425
426         long now = System.currentTimeMillis();
427         if (parserClass == null)
428         {
429             // try to get the name of the SAX driver from the System properties
430
String JavaDoc driver;
431             try
432             {
433                 driver = System.getProperty("sax.driver", DEFAULT_PARSER);
434             }
435             catch (SecurityException JavaDoc e)
436             {
437                 // An unsigned applet may not access system properties.
438
driver = DEFAULT_PARSER;
439             }
440             setDriver(driver);
441         }
442
443         Parser JavaDoc parser = null;
444         try
445         {
446             parser = (Parser JavaDoc) parserClass.newInstance();
447         }
448         catch (NoSuchMethodError JavaDoc nsm)
449         {
450             // This is thrown if no constructor exists for the parser class
451
// and is transformed into a regular exception.
452
throw new Exception JavaDoc("Can't create Parser: " + parserClass);
453         }
454
455         parser.setDocumentHandler(this);
456         parser.setErrorHandler(this);
457
458         if (debug)
459         {
460             System.out.println("Beginning parsing XML input stream");
461         }
462         try
463         {
464             if(inputEncoding == null)
465             {
466               parser.parse(new InputSource JavaDoc(is));
467             }
468             else
469             {
470               parser.parse( new InputSource JavaDoc( new InputStreamReader JavaDoc(is, inputEncoding)));
471             }
472         }
473         finally
474         {
475             // Clear any huge buffers.
476
if (cdata.length() > 128 * 4)
477             {
478                 // Exceeded original capacity by greater than 4x; release
479
// buffer to prevent leakage.
480
cdata = null;
481             }
482         }
483         if (debug)
484         {
485             System.out.println ("Spent " + (System.currentTimeMillis() - now)
486                     + " millis parsing");
487         }
488     }
489
490     /**
491      * This method is called when a root level object has been parsed.
492      * Sub-classes implement this callback to receive the fully parsed
493      * object.
494      */

495     protected abstract void objectParsed(Object JavaDoc what);
496
497
498     ////////////////////////////////////////////////////////////////
499
// methods called by XML parser
500

501     /**
502      * Method called by SAX driver.
503      */

504     public void characters(char ch[], int start, int length)
505             throws SAXException JavaDoc
506     {
507         if (readCdata)
508         {
509             cdata.append(ch, start, length);
510         }
511     }
512
513     /**
514      * Method called by SAX driver.
515      */

516     public void endElement(String JavaDoc name) throws SAXException JavaDoc
517     {
518
519         if (debug)
520         {
521             System.out.println("endElement: " + name);
522         }
523
524         // finalize character data, if appropriate
525
if (currentValue != null && readCdata)
526         {
527             currentValue.characterData(cdata.toString());
528             cdata.setLength(0);
529             readCdata = false;
530         }
531
532         if ("value".equals(name))
533         {
534             // Only handle top level objects or objects contained in
535
// arrays here. For objects contained in structs, wait
536
// for </member> (see code below).
537
int depth = values.size();
538             if (depth < 2 || values.elementAt(depth - 2).hashCode() != STRUCT)
539             {
540                 Value v = currentValue;
541                 values.pop();
542                 if (depth < 2)
543                 {
544                     // This is a top-level object
545
objectParsed(v.value);
546                     currentValue = null;
547                 }
548                 else
549                 {
550                     // Add object to sub-array; if current container
551
// is a struct, add later (at </member>).
552
currentValue = (Value) values.peek();
553                     currentValue.endElement(v);
554                 }
555             }
556         }
557
558         // Handle objects contained in structs.
559
if ("member".equals(name))
560         {
561             Value v = currentValue;
562             values.pop();
563             currentValue = (Value) values.peek();
564             currentValue.endElement(v);
565         }
566
567         else if ("methodName".equals(name))
568         {
569             methodName = cdata.toString();
570             cdata.setLength(0);
571             readCdata = false;
572         }
573     }
574
575     /**
576      * Method called by SAX driver.
577      */

578     public void startElement(String JavaDoc name, AttributeList JavaDoc atts)
579             throws SAXException JavaDoc
580     {
581         if (debug)
582         {
583             System.out.println("startElement: " + name);
584         }
585
586         if ("value".equals(name))
587         {
588             Value v = new Value();
589             values.push(v);
590             currentValue = v;
591             // cdata object is reused
592
cdata.setLength(0);
593             readCdata = true;
594         }
595         else if ("methodName".equals(name))
596         {
597             cdata.setLength(0);
598             readCdata = true;
599         }
600         else if ("name".equals(name))
601         {
602             cdata.setLength(0);
603             readCdata = true;
604         }
605         else if ("string".equals(name))
606         {
607             // currentValue.setType (STRING);
608
cdata.setLength(0);
609             readCdata = true;
610         }
611         else if ("i4".equals(name) || "int".equals(name))
612         {
613             currentValue.setType(INTEGER);
614             cdata.setLength(0);
615             readCdata = true;
616         }
617         else if ("boolean".equals(name))
618         {
619             currentValue.setType(BOOLEAN);
620             cdata.setLength(0);
621             readCdata = true;
622         }
623         else if ("double".equals(name))
624         {
625             currentValue.setType(DOUBLE);
626             cdata.setLength(0);
627             readCdata = true;
628         }
629         else if ("dateTime.iso8601".equals(name))
630         {
631             currentValue.setType(DATE);
632             cdata.setLength(0);
633             readCdata = true;
634         }
635         else if ("base64".equals(name))
636         {
637             currentValue.setType(BASE64);
638             cdata.setLength(0);
639             readCdata = true;
640         }
641         else if ("struct".equals(name))
642         {
643             currentValue.setType(STRUCT);
644         }
645         else if ("array".equals(name))
646         {
647             currentValue.setType(ARRAY);
648         }
649     }
650
651     /**
652      *
653      * @param e
654      * @throws SAXException
655      */

656     public void error(SAXParseException JavaDoc e) throws SAXException JavaDoc
657     {
658         System.err.println("Error parsing XML: " + e);
659         errorLevel = RECOVERABLE;
660         errorMsg = e.toString();
661     }
662
663     /**
664      *
665      * @param e
666      * @throws SAXException
667      */

668     public void fatalError(SAXParseException JavaDoc e) throws SAXException JavaDoc
669     {
670         System.err.println("Fatal error parsing XML: " + e);
671         errorLevel = FATAL;
672         errorMsg = e.toString();
673     }
674
675     /**
676      * This represents a XML-RPC value parsed from the request.
677      */

678     class Value
679     {
680         int type;
681         Object JavaDoc value;
682         // the name to use for the next member of struct values
683
String JavaDoc nextMemberName;
684
685         Hashtable JavaDoc struct;
686         Vector JavaDoc array;
687
688         /**
689          * Constructor.
690          */

691         public Value()
692         {
693             this.type = STRING;
694         }
695
696         /**
697          * Notification that a new child element has been parsed.
698          */

699         public void endElement(Value child)
700         {
701             switch (type)
702             {
703                 case ARRAY:
704                     array.addElement(child.value);
705                     break;
706                 case STRUCT:
707                     struct.put(nextMemberName, child.value);
708             }
709         }
710
711         /**
712          * Set the type of this value. If it's a container, create the
713          * corresponding java container.
714          */

715         public void setType(int type)
716         {
717             //System.out.println ("setting type to "+types[type]);
718
this.type = type;
719             switch (type)
720             {
721                 case ARRAY:
722                     value = array = new Vector JavaDoc();
723                     break;
724                 case STRUCT:
725                     value = struct = new Hashtable JavaDoc();
726                     break;
727             }
728         }
729
730         /**
731          * Set the character data for the element and interpret it
732          * according to the element type.
733          */

734         public void characterData(String JavaDoc cdata)
735         {
736             switch (type)
737             {
738                 case INTEGER:
739                     value = typeFactory.createInteger(cdata);
740                     break;
741                 case BOOLEAN:
742                     value = typeFactory.createBoolean(cdata);
743                     break;
744                 case DOUBLE:
745                     value = typeFactory.createDouble(cdata);
746                     break;
747                 case DATE:
748                     value = typeFactory.createDate(cdata);
749                     break;
750                 case BASE64:
751                     value = typeFactory.createBase64(cdata);
752                     break;
753                 case STRING:
754                     value = typeFactory.createString(cdata);
755                     break;
756                 case STRUCT:
757                     // this is the name to use for the next member of this struct
758
nextMemberName = cdata;
759                     break;
760             }
761         }
762
763         /**
764          * This is a performance hack to get the type of a value
765          * without casting the Object. It breaks the contract of
766          * method hashCode, but it doesn't matter since Value objects
767          * are never used as keys in Hashtables.
768          */

769         public int hashCode()
770         {
771             return type;
772         }
773
774         /**
775          *
776          * @return
777          */

778         public String JavaDoc toString()
779         {
780             return (types[type] + " element " + value);
781         }
782     }
783 }
784
Popular Tags