Home | History | Annotate | Download | only in provider
      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 (at) example.com' from='mary (at) 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      */
    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      */
    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 
    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 (at) example.com' from='mary (at) 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      */
    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      */
    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      */
    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      */
    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 (at) 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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    434     private ProviderManager() {
    435         super();
    436         initialize();
    437     }
    438 }