• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * $RCSfile$
3  * $Revision$
4  * $Date$
5  *
6  * Copyright 2003-2007 Jive Software.
7  *
8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 
21 package org.jivesoftware.smack.provider;
22 
23 import org.jivesoftware.smack.packet.IQ;
24 import org.jivesoftware.smack.packet.PacketExtension;
25 import org.xmlpull.v1.XmlPullParserFactory;
26 import org.xmlpull.v1.XmlPullParser;
27 
28 import java.io.InputStream;
29 import java.net.URL;
30 import java.util.*;
31 import java.util.concurrent.ConcurrentHashMap;
32 
33 /**
34  * Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of
35  * providers exist:<ul>
36  *      <li>IQProvider -- parses IQ requests into Java objects.
37  *      <li>PacketExtension -- parses XML sub-documents attached to packets into
38  *          PacketExtension instances.</ul>
39  *
40  * <b>IQProvider</b><p>
41  *
42  * By default, Smack only knows how to process IQ packets with sub-packets that
43  * are in a few namespaces such as:<ul>
44  *      <li>jabber:iq:auth
45  *      <li>jabber:iq:roster
46  *      <li>jabber:iq:register</ul>
47  *
48  * Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing
49  * mechanism is provided. IQ providers are registered programatically or by creating a
50  * smack.providers file in the META-INF directory of your JAR file. The file is an XML
51  * document that contains one or more iqProvider entries, as in the following example:
52  *
53  * <pre>
54  * &lt;?xml version="1.0"?&gt;
55  * &lt;smackProviders&gt;
56  *     &lt;iqProvider&gt;
57  *         &lt;elementName&gt;query&lt;/elementName&gt;
58  *         &lt;namespace&gt;jabber:iq:time&lt;/namespace&gt;
59  *         &lt;className&gt;org.jivesoftware.smack.packet.Time&lt/className&gt;
60  *     &lt;/iqProvider&gt;
61  * &lt;/smackProviders&gt;</pre>
62  *
63  * Each IQ provider is associated with an element name and a namespace. If multiple provider
64  * entries attempt to register to handle the same namespace, the first entry loaded from the
65  * classpath will take precedence. The IQ provider class can either implement the IQProvider
66  * interface, or extend the IQ class. In the former case, each IQProvider is responsible for
67  * parsing the raw XML stream to create an IQ instance. In the latter case, bean introspection
68  * is used to try to automatically set properties of the IQ instance using the values found
69  * in the IQ packet XML. For example, an XMPP time packet resembles the following:
70  * <pre>
71  * &lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
72  *     &lt;query xmlns='jabber:iq:time'&gt;
73  *         &lt;utc&gt;20020910T17:58:35&lt;/utc&gt;
74  *         &lt;tz&gt;MDT&lt;/tz&gt;
75  *         &lt;display&gt;Tue Sep 10 12:58:35 2002&lt;/display&gt;
76  *     &lt;/query&gt;
77  * &lt;/iq&gt;</pre>
78  *
79  * In order for this packet to be automatically mapped to the Time object listed in the
80  * providers file above, it must have the methods setUtc(String), setTz(String), and
81  * setDisplay(String). The introspection service will automatically try to convert the String
82  * value from the XML into a boolean, int, long, float, double, or Class depending on the
83  * type the IQ instance expects.<p>
84  *
85  * A pluggable system for packet extensions, child elements in a custom namespace for
86  * message and presence packets, also exists. Each extension provider
87  * is registered with a name space in the smack.providers file as in the following example:
88  *
89  * <pre>
90  * &lt;?xml version="1.0"?&gt;
91  * &lt;smackProviders&gt;
92  *     &lt;extensionProvider&gt;
93  *         &lt;elementName&gt;x&lt;/elementName&gt;
94  *         &lt;namespace&gt;jabber:iq:event&lt;/namespace&gt;
95  *         &lt;className&gt;org.jivesoftware.smack.packet.MessageEvent&lt/className&gt;
96  *     &lt;/extensionProvider&gt;
97  * &lt;/smackProviders&gt;</pre>
98  *
99  * If multiple provider entries attempt to register to handle the same element name and namespace,
100  * the first entry loaded from the classpath will take precedence. Whenever a packet extension
101  * is found in a packet, parsing will be passed to the correct provider. Each provider
102  * can either implement the PacketExtensionProvider interface or be a standard Java Bean. In
103  * the former case, each extension provider is responsible for parsing the raw XML stream to
104  * contruct an object. In the latter case, bean introspection is used to try to automatically
105  * set the properties of the class using the values in the packet extension sub-element. When an
106  * extension provider is not registered for an element name and namespace combination, Smack will
107  * store all top-level elements of the sub-packet in DefaultPacketExtension object and then
108  * attach it to the packet.<p>
109  *
110  * It is possible to provide a custom provider manager instead of the default implementation
111  * provided by Smack. If you want to provide your own provider manager then you need to do it
112  * before creating any {@link org.jivesoftware.smack.Connection} by sending the static
113  * {@link #setInstance(ProviderManager)} message. Trying to change the provider manager after
114  * an Connection was created will result in an {@link IllegalStateException} error.
115  *
116  * @author Matt Tucker
117  */
118 public class ProviderManager {
119 
120     private static ProviderManager instance;
121 
122     private Map<String, Object> extensionProviders = new ConcurrentHashMap<String, Object>();
123     private Map<String, Object> iqProviders = new ConcurrentHashMap<String, Object>();
124 
125     /**
126      * Returns the only ProviderManager valid instance.  Use {@link #setInstance(ProviderManager)}
127      * to configure your own provider manager. If non was provided then an instance of this
128      * class will be used.
129      *
130      * @return the only ProviderManager valid instance.
131      */
getInstance()132     public static synchronized ProviderManager getInstance() {
133         if (instance == null) {
134             instance = new ProviderManager();
135         }
136         return instance;
137     }
138 
139     /**
140      * Sets the only ProviderManager valid instance to be used by all Connections. If you
141      * want to provide your own provider manager then you need to do it before creating
142      * any Connection. Otherwise an IllegalStateException will be thrown.
143      *
144      * @param providerManager the only ProviderManager valid instance to be used.
145      * @throws IllegalStateException if a provider manager was already configued.
146      */
setInstance(ProviderManager providerManager)147     public static synchronized void setInstance(ProviderManager providerManager) {
148         if (instance != null) {
149             throw new IllegalStateException("ProviderManager singleton already set");
150         }
151         instance = providerManager;
152     }
153 
initialize()154     protected void initialize() {
155         // Load IQ processing providers.
156         try {
157             // Get an array of class loaders to try loading the providers files from.
158             ClassLoader[] classLoaders = getClassLoaders();
159             for (ClassLoader classLoader : classLoaders) {
160                 Enumeration<URL> providerEnum = classLoader.getResources(
161                         "META-INF/smack.providers");
162                 while (providerEnum.hasMoreElements()) {
163                     URL url = providerEnum.nextElement();
164                     InputStream providerStream = null;
165                     try {
166                         providerStream = url.openStream();
167                         XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
168                         parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
169                         parser.setInput(providerStream, "UTF-8");
170                         int eventType = parser.getEventType();
171                         do {
172                             if (eventType == XmlPullParser.START_TAG) {
173                                 if (parser.getName().equals("iqProvider")) {
174                                     parser.next();
175                                     parser.next();
176                                     String elementName = parser.nextText();
177                                     parser.next();
178                                     parser.next();
179                                     String namespace = parser.nextText();
180                                     parser.next();
181                                     parser.next();
182                                     String className = parser.nextText();
183                                     // Only add the provider for the namespace if one isn't
184                                     // already registered.
185                                     String key = getProviderKey(elementName, namespace);
186                                     if (!iqProviders.containsKey(key)) {
187                                         // Attempt to load the provider class and then create
188                                         // a new instance if it's an IQProvider. Otherwise, if it's
189                                         // an IQ class, add the class object itself, then we'll use
190                                         // reflection later to create instances of the class.
191                                         try {
192                                             // Add the provider to the map.
193                                             Class<?> provider = Class.forName(className);
194                                             if (IQProvider.class.isAssignableFrom(provider)) {
195                                                 iqProviders.put(key, provider.newInstance());
196                                             }
197                                             else if (IQ.class.isAssignableFrom(provider)) {
198                                                 iqProviders.put(key, provider);
199                                             }
200                                         }
201                                         catch (ClassNotFoundException cnfe) {
202                                             cnfe.printStackTrace();
203                                         }
204                                     }
205                                 }
206                                 else if (parser.getName().equals("extensionProvider")) {
207                                     parser.next();
208                                     parser.next();
209                                     String elementName = parser.nextText();
210                                     parser.next();
211                                     parser.next();
212                                     String namespace = parser.nextText();
213                                     parser.next();
214                                     parser.next();
215                                     String className = parser.nextText();
216                                     // Only add the provider for the namespace if one isn't
217                                     // already registered.
218                                     String key = getProviderKey(elementName, namespace);
219                                     if (!extensionProviders.containsKey(key)) {
220                                         // Attempt to load the provider class and then create
221                                         // a new instance if it's a Provider. Otherwise, if it's
222                                         // a PacketExtension, add the class object itself and
223                                         // then we'll use reflection later to create instances
224                                         // of the class.
225                                         try {
226                                             // Add the provider to the map.
227                                             Class<?> provider = Class.forName(className);
228                                             if (PacketExtensionProvider.class.isAssignableFrom(
229                                                     provider)) {
230                                                 extensionProviders.put(key, provider.newInstance());
231                                             }
232                                             else if (PacketExtension.class.isAssignableFrom(
233                                                     provider)) {
234                                                 extensionProviders.put(key, provider);
235                                             }
236                                         }
237                                         catch (ClassNotFoundException cnfe) {
238                                             cnfe.printStackTrace();
239                                         }
240                                     }
241                                 }
242                             }
243                             eventType = parser.next();
244                         }
245                         while (eventType != XmlPullParser.END_DOCUMENT);
246                     }
247                     finally {
248                         try {
249                             providerStream.close();
250                         }
251                         catch (Exception e) {
252                             // Ignore.
253                         }
254                     }
255                 }
256             }
257         }
258         catch (Exception e) {
259             e.printStackTrace();
260         }
261     }
262 
263     /**
264      * Returns the IQ provider registered to the specified XML element name and namespace.
265      * For example, if a provider was registered to the element name "query" and the
266      * namespace "jabber:iq:time", then the following packet would trigger the provider:
267      *
268      * <pre>
269      * &lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
270      *     &lt;query xmlns='jabber:iq:time'&gt;
271      *         &lt;utc&gt;20020910T17:58:35&lt;/utc&gt;
272      *         &lt;tz&gt;MDT&lt;/tz&gt;
273      *         &lt;display&gt;Tue Sep 10 12:58:35 2002&lt;/display&gt;
274      *     &lt;/query&gt;
275      * &lt;/iq&gt;</pre>
276      *
277      * <p>Note: this method is generally only called by the internal Smack classes.
278      *
279      * @param elementName the XML element name.
280      * @param namespace the XML namespace.
281      * @return the IQ provider.
282      */
getIQProvider(String elementName, String namespace)283     public Object getIQProvider(String elementName, String namespace) {
284         String key = getProviderKey(elementName, namespace);
285         return iqProviders.get(key);
286     }
287 
288     /**
289      * Returns an unmodifiable collection of all IQProvider instances. Each object
290      * in the collection will either be an IQProvider instance, or a Class object
291      * that implements the IQProvider interface.
292      *
293      * @return all IQProvider instances.
294      */
getIQProviders()295     public Collection<Object> getIQProviders() {
296         return Collections.unmodifiableCollection(iqProviders.values());
297     }
298 
299     /**
300      * Adds an IQ provider (must be an instance of IQProvider or Class object that is an IQ)
301      * with the specified element name and name space. The provider will override any providers
302      * loaded through the classpath.
303      *
304      * @param elementName the XML element name.
305      * @param namespace the XML namespace.
306      * @param provider the IQ provider.
307      */
addIQProvider(String elementName, String namespace, Object provider)308     public void addIQProvider(String elementName, String namespace,
309             Object provider)
310     {
311         if (!(provider instanceof IQProvider || (provider instanceof Class &&
312                 IQ.class.isAssignableFrom((Class<?>)provider))))
313         {
314             throw new IllegalArgumentException("Provider must be an IQProvider " +
315                     "or a Class instance.");
316         }
317         String key = getProviderKey(elementName, namespace);
318         iqProviders.put(key, provider);
319     }
320 
321     /**
322      * Removes an IQ provider with the specified element name and namespace. This
323      * method is typically called to cleanup providers that are programatically added
324      * using the {@link #addIQProvider(String, String, Object) addIQProvider} method.
325      *
326      * @param elementName the XML element name.
327      * @param namespace the XML namespace.
328      */
removeIQProvider(String elementName, String namespace)329     public void removeIQProvider(String elementName, String namespace) {
330         String key = getProviderKey(elementName, namespace);
331         iqProviders.remove(key);
332     }
333 
334     /**
335      * Returns the packet extension provider registered to the specified XML element name
336      * and namespace. For example, if a provider was registered to the element name "x" and the
337      * namespace "jabber:x:event", then the following packet would trigger the provider:
338      *
339      * <pre>
340      * &lt;message to='romeo@montague.net' id='message_1'&gt;
341      *     &lt;body&gt;Art thou not Romeo, and a Montague?&lt;/body&gt;
342      *     &lt;x xmlns='jabber:x:event'&gt;
343      *         &lt;composing/&gt;
344      *     &lt;/x&gt;
345      * &lt;/message&gt;</pre>
346      *
347      * <p>Note: this method is generally only called by the internal Smack classes.
348      *
349      * @param elementName element name associated with extension provider.
350      * @param namespace namespace associated with extension provider.
351      * @return the extenion provider.
352      */
getExtensionProvider(String elementName, String namespace)353     public Object getExtensionProvider(String elementName, String namespace) {
354         String key = getProviderKey(elementName, namespace);
355         return extensionProviders.get(key);
356     }
357 
358     /**
359      * Adds an extension provider with the specified element name and name space. The provider
360      * will override any providers loaded through the classpath. The provider must be either
361      * a PacketExtensionProvider instance, or a Class object of a Javabean.
362      *
363      * @param elementName the XML element name.
364      * @param namespace the XML namespace.
365      * @param provider the extension provider.
366      */
addExtensionProvider(String elementName, String namespace, Object provider)367     public void addExtensionProvider(String elementName, String namespace,
368             Object provider)
369     {
370         if (!(provider instanceof PacketExtensionProvider || provider instanceof Class)) {
371             throw new IllegalArgumentException("Provider must be a PacketExtensionProvider " +
372                     "or a Class instance.");
373         }
374         String key = getProviderKey(elementName, namespace);
375         extensionProviders.put(key, provider);
376     }
377 
378     /**
379      * Removes an extension provider with the specified element name and namespace. This
380      * method is typically called to cleanup providers that are programatically added
381      * using the {@link #addExtensionProvider(String, String, Object) addExtensionProvider} method.
382      *
383      * @param elementName the XML element name.
384      * @param namespace the XML namespace.
385      */
removeExtensionProvider(String elementName, String namespace)386     public void removeExtensionProvider(String elementName, String namespace) {
387         String key = getProviderKey(elementName, namespace);
388         extensionProviders.remove(key);
389     }
390 
391     /**
392      * Returns an unmodifiable collection of all PacketExtensionProvider instances. Each object
393      * in the collection will either be a PacketExtensionProvider instance, or a Class object
394      * that implements the PacketExtensionProvider interface.
395      *
396      * @return all PacketExtensionProvider instances.
397      */
getExtensionProviders()398     public Collection<Object> getExtensionProviders() {
399         return Collections.unmodifiableCollection(extensionProviders.values());
400     }
401 
402     /**
403      * Returns a String key for a given element name and namespace.
404      *
405      * @param elementName the element name.
406      * @param namespace the namespace.
407      * @return a unique key for the element name and namespace pair.
408      */
getProviderKey(String elementName, String namespace)409     private String getProviderKey(String elementName, String namespace) {
410         StringBuilder buf = new StringBuilder();
411         buf.append("<").append(elementName).append("/><").append(namespace).append("/>");
412         return buf.toString();
413     }
414 
415     /**
416      * Returns an array of class loaders to load resources from.
417      *
418      * @return an array of ClassLoader instances.
419      */
getClassLoaders()420     private ClassLoader[] getClassLoaders() {
421         ClassLoader[] classLoaders = new ClassLoader[2];
422         classLoaders[0] = ProviderManager.class.getClassLoader();
423         classLoaders[1] = Thread.currentThread().getContextClassLoader();
424         // Clean up possible null values. Note that #getClassLoader may return a null value.
425         List<ClassLoader> loaders = new ArrayList<ClassLoader>();
426         for (ClassLoader classLoader : classLoaders) {
427             if (classLoader != null) {
428                 loaders.add(classLoader);
429             }
430         }
431         return loaders.toArray(new ClassLoader[loaders.size()]);
432     }
433 
ProviderManager()434     private ProviderManager() {
435         super();
436         initialize();
437     }
438 }