ProviderManager.java

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

  17. package org.jivesoftware.smack.provider;

  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.concurrent.ConcurrentHashMap;

  22. import org.jivesoftware.smack.SmackConfiguration;
  23. import org.jivesoftware.smack.packet.IQ;
  24. import org.jivesoftware.smack.packet.ExtensionElement;
  25. import org.jivesoftware.smack.util.StringUtils;
  26. import org.jxmpp.util.XmppStringUtils;

  27. /**
  28.  * Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of
  29.  * providers exist:<ul>
  30.  *      <li>IQProvider -- parses IQ requests into Java objects.
  31.  *      <li>PacketExtension -- parses XML sub-documents attached to packets into
  32.  *          PacketExtension instances.</ul>
  33.  *
  34.  * <b>IQProvider</b><p>
  35.  *
  36.  * By default, Smack only knows how to process IQ packets with sub-packets that
  37.  * are in a few namespaces such as:<ul>
  38.  *      <li>jabber:iq:auth
  39.  *      <li>jabber:iq:roster
  40.  *      <li>jabber:iq:register</ul>
  41.  *
  42.  * Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing
  43.  * mechanism is provided. IQ providers are registered programatically or by creating a
  44.  * providers file. The file is an XML
  45.  * document that contains one or more iqProvider entries, as in the following example:
  46.  *
  47.  * <pre>
  48.  * &lt;?xml version="1.0"?&gt;
  49.  * &lt;smackProviders&gt;
  50.  *     &lt;iqProvider&gt;
  51.  *         &lt;elementName&gt;query&lt;/elementName&gt;
  52.  *         &lt;namespace&gt;jabber:iq:time&lt;/namespace&gt;
  53.  *         &lt;className&gt;org.jivesoftware.smack.packet.Time&lt/className&gt;
  54.  *     &lt;/iqProvider&gt;
  55.  * &lt;/smackProviders&gt;</pre>
  56.  *
  57.  * Each IQ provider is associated with an element name and a namespace. If multiple provider
  58.  * entries attempt to register to handle the same namespace, the first entry loaded from the
  59.  * classpath will take precedence. The IQ provider class can either implement the IQProvider
  60.  * interface, or extend the IQ class. In the former case, each IQProvider is responsible for
  61.  * parsing the raw XML stream to create an IQ instance. In the latter case, bean introspection
  62.  * is used to try to automatically set properties of the IQ instance using the values found
  63.  * in the IQ packet XML. For example, an XMPP time packet resembles the following:
  64.  * <pre>
  65.  * &lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
  66.  *     &lt;query xmlns='jabber:iq:time'&gt;
  67.  *         &lt;utc&gt;20020910T17:58:35&lt;/utc&gt;
  68.  *         &lt;tz&gt;MDT&lt;/tz&gt;
  69.  *         &lt;display&gt;Tue Sep 10 12:58:35 2002&lt;/display&gt;
  70.  *     &lt;/query&gt;
  71.  * &lt;/iq&gt;</pre>
  72.  *
  73.  * In order for this packet to be automatically mapped to the Time object listed in the
  74.  * providers file above, it must have the methods setUtc(String), setTz(String), and
  75.  * setDisplay(String). The introspection service will automatically try to convert the String
  76.  * value from the XML into a boolean, int, long, float, double, or Class depending on the
  77.  * type the IQ instance expects.<p>
  78.  *
  79.  * A pluggable system for packet extensions, child elements in a custom namespace for
  80.  * message and presence packets, also exists. Each extension provider
  81.  * is registered with a name space in the smack.providers file as in the following example:
  82.  *
  83.  * <pre>
  84.  * &lt;?xml version="1.0"?&gt;
  85.  * &lt;smackProviders&gt;
  86.  *     &lt;extensionProvider&gt;
  87.  *         &lt;elementName&gt;x&lt;/elementName&gt;
  88.  *         &lt;namespace&gt;jabber:iq:event&lt;/namespace&gt;
  89.  *         &lt;className&gt;org.jivesoftware.smack.packet.MessageEvent&lt/className&gt;
  90.  *     &lt;/extensionProvider&gt;
  91.  * &lt;/smackProviders&gt;</pre>
  92.  *
  93.  * If multiple provider entries attempt to register to handle the same element name and namespace,
  94.  * the first entry loaded from the classpath will take precedence. Whenever a packet extension
  95.  * is found in a packet, parsing will be passed to the correct provider. Each provider
  96.  * can either implement the PacketExtensionProvider interface or be a standard Java Bean. In
  97.  * the former case, each extension provider is responsible for parsing the raw XML stream to
  98.  * contruct an object. In the latter case, bean introspection is used to try to automatically
  99.  * set the properties of th class using the values in the packet extension sub-element. When an
  100.  * extension provider is not registered for an element name and namespace combination, Smack will
  101.  * store all top-level elements of the sub-packet in DefaultPacketExtension object and then
  102.  * attach it to the packet.<p>
  103.  *
  104.  * @author Matt Tucker
  105.  */
  106. public final class ProviderManager {

  107.     private static final Map<String, ExtensionElementProvider<ExtensionElement>> extensionProviders = new ConcurrentHashMap<String, ExtensionElementProvider<ExtensionElement>>();
  108.     private static final Map<String, IQProvider<IQ>> iqProviders = new ConcurrentHashMap<String, IQProvider<IQ>>();
  109.     private static final Map<String, ExtensionElementProvider<ExtensionElement>> streamFeatureProviders = new ConcurrentHashMap<String, ExtensionElementProvider<ExtensionElement>>();

  110.     static {
  111.         // Ensure that Smack is initialized by calling getVersion, so that user
  112.         // registered providers do not get overwritten by a following Smack
  113.         // initialization. This guarantees that Smack is initialized before a
  114.         // new provider is registered
  115.         SmackConfiguration.getVersion();
  116.     }

  117.     @SuppressWarnings("unchecked")
  118.     public static void addLoader(ProviderLoader loader) {
  119.         if (loader.getIQProviderInfo() != null) {
  120.             for (IQProviderInfo info : loader.getIQProviderInfo()) {
  121.                 addIQProvider(info.getElementName(), info.getNamespace(), info.getProvider());
  122.             }
  123.         }
  124.        
  125.         if (loader.getExtensionProviderInfo() != null) {
  126.             for (ExtensionProviderInfo info : loader.getExtensionProviderInfo()) {
  127.                 addExtensionProvider(info.getElementName(), info.getNamespace(), info.getProvider());
  128.             }
  129.         }

  130.         if (loader.getStreamFeatureProviderInfo() != null) {
  131.             for (StreamFeatureProviderInfo info : loader.getStreamFeatureProviderInfo()) {
  132.                 addStreamFeatureProvider(info.getElementName(), info.getNamespace(),
  133.                                 (ExtensionElementProvider<ExtensionElement>) info.getProvider());
  134.             }
  135.         }
  136.     }
  137.    
  138.     /**
  139.      * Returns the IQ provider registered to the specified XML element name and namespace.
  140.      * For example, if a provider was registered to the element name "query" and the
  141.      * namespace "jabber:iq:time", then the following packet would trigger the provider:
  142.      *
  143.      * <pre>
  144.      * &lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
  145.      *     &lt;query xmlns='jabber:iq:time'&gt;
  146.      *         &lt;utc&gt;20020910T17:58:35&lt;/utc&gt;
  147.      *         &lt;tz&gt;MDT&lt;/tz&gt;
  148.      *         &lt;display&gt;Tue Sep 10 12:58:35 2002&lt;/display&gt;
  149.      *     &lt;/query&gt;
  150.      * &lt;/iq&gt;</pre>
  151.      *
  152.      * <p>Note: this method is generally only called by the internal Smack classes.
  153.      *
  154.      * @param elementName the XML element name.
  155.      * @param namespace the XML namespace.
  156.      * @return the IQ provider.
  157.      */
  158.     public static IQProvider<IQ> getIQProvider(String elementName, String namespace) {
  159.         String key = getKey(elementName, namespace);
  160.         return iqProviders.get(key);
  161.     }

  162.     /**
  163.      * Returns an unmodifiable collection of all IQProvider instances. Each object
  164.      * in the collection will either be an IQProvider instance, or a Class object
  165.      * that implements the IQProvider interface.
  166.      *
  167.      * @return all IQProvider instances.
  168.      */
  169.     public static List<IQProvider<IQ>> getIQProviders() {
  170.         List<IQProvider<IQ>> providers = new ArrayList<>(iqProviders.size());
  171.         providers.addAll(iqProviders.values());
  172.         return providers;
  173.     }

  174.     /**
  175.      * Adds an IQ provider (must be an instance of IQProvider or Class object that is an IQ)
  176.      * with the specified element name and name space. The provider will override any providers
  177.      * loaded through the classpath.
  178.      *
  179.      * @param elementName the XML element name.
  180.      * @param namespace the XML namespace.
  181.      * @param provider the IQ provider.
  182.      */
  183.     @SuppressWarnings("unchecked")
  184.     public static void addIQProvider(String elementName, String namespace,
  185.             Object provider)
  186.     {
  187.         validate(elementName, namespace);
  188.         // First remove existing providers
  189.         String key = removeIQProvider(elementName, namespace);
  190.         if (provider instanceof IQProvider) {
  191.             iqProviders.put(key, (IQProvider<IQ>) provider);
  192.         } else {
  193.             throw new IllegalArgumentException("Provider must be an IQProvider");
  194.         }
  195.     }

  196.     /**
  197.      * Removes an IQ provider with the specified element name and namespace. This
  198.      * method is typically called to cleanup providers that are programatically added
  199.      * using the {@link #addIQProvider(String, String, Object) addIQProvider} method.
  200.      *
  201.      * @param elementName the XML element name.
  202.      * @param namespace the XML namespace.
  203.      * @return the key of the removed IQ Provider
  204.      */
  205.     public static String removeIQProvider(String elementName, String namespace) {
  206.         String key = getKey(elementName, namespace);
  207.         iqProviders.remove(key);
  208.         return key;
  209.     }

  210.     /**
  211.      * Returns the packet extension provider registered to the specified XML element name
  212.      * and namespace. For example, if a provider was registered to the element name "x" and the
  213.      * namespace "jabber:x:event", then the following packet would trigger the provider:
  214.      *
  215.      * <pre>
  216.      * &lt;message to='romeo@montague.net' id='message_1'&gt;
  217.      *     &lt;body&gt;Art thou not Romeo, and a Montague?&lt;/body&gt;
  218.      *     &lt;x xmlns='jabber:x:event'&gt;
  219.      *         &lt;composing/&gt;
  220.      *     &lt;/x&gt;
  221.      * &lt;/message&gt;</pre>
  222.      *
  223.      * <p>Note: this method is generally only called by the internal Smack classes.
  224.      *
  225.      * @param elementName element name associated with extension provider.
  226.      * @param namespace namespace associated with extension provider.
  227.      * @return the extenion provider.
  228.      */
  229.     public static ExtensionElementProvider<ExtensionElement> getExtensionProvider(String elementName, String namespace) {
  230.         String key = getKey(elementName, namespace);
  231.         return extensionProviders.get(key);
  232.     }

  233.     /**
  234.      * Adds an extension provider with the specified element name and name space. The provider
  235.      * will override any providers loaded through the classpath. The provider must be either
  236.      * a PacketExtensionProvider instance, or a Class object of a Javabean.
  237.      *
  238.      * @param elementName the XML element name.
  239.      * @param namespace the XML namespace.
  240.      * @param provider the extension provider.
  241.      */
  242.     @SuppressWarnings("unchecked")
  243.     public static void addExtensionProvider(String elementName, String namespace,
  244.             Object provider)
  245.     {
  246.         validate(elementName, namespace);
  247.         // First remove existing providers
  248.         String key = removeExtensionProvider(elementName, namespace);
  249.         if (provider instanceof ExtensionElementProvider) {
  250.             extensionProviders.put(key, (ExtensionElementProvider<ExtensionElement>) provider);
  251.         } else {
  252.             throw new IllegalArgumentException("Provider must be a PacketExtensionProvider");
  253.         }
  254.     }

  255.     /**
  256.      * Removes an extension provider with the specified element name and namespace. This
  257.      * method is typically called to cleanup providers that are programatically added
  258.      * using the {@link #addExtensionProvider(String, String, Object) addExtensionProvider} method.
  259.      *
  260.      * @param elementName the XML element name.
  261.      * @param namespace the XML namespace.
  262.      * @return the key of the removed packet extension provider
  263.      */
  264.     public static String removeExtensionProvider(String elementName, String namespace) {
  265.         String key = getKey(elementName, namespace);
  266.         extensionProviders.remove(key);
  267.         return key;
  268.     }

  269.     /**
  270.      * Returns an unmodifiable collection of all PacketExtensionProvider instances. Each object
  271.      * in the collection will either be a PacketExtensionProvider instance, or a Class object
  272.      * that implements the PacketExtensionProvider interface.
  273.      *
  274.      * @return all PacketExtensionProvider instances.
  275.      */
  276.     public static List<ExtensionElementProvider<ExtensionElement>> getExtensionProviders() {
  277.         List<ExtensionElementProvider<ExtensionElement>> providers = new ArrayList<>(extensionProviders.size());
  278.         providers.addAll(extensionProviders.values());
  279.         return providers;
  280.     }

  281.     public static ExtensionElementProvider<ExtensionElement> getStreamFeatureProvider(String elementName, String namespace) {
  282.         String key = getKey(elementName, namespace);
  283.         return streamFeatureProviders.get(key);
  284.     }

  285.     public static void addStreamFeatureProvider(String elementName, String namespace, ExtensionElementProvider<ExtensionElement> provider) {
  286.         validate(elementName, namespace);
  287.         String key = getKey(elementName, namespace);
  288.         streamFeatureProviders.put(key, provider);
  289.     }

  290.     public static void removeStreamFeatureProvider(String elementName, String namespace) {
  291.         String key = getKey(elementName, namespace);
  292.         streamFeatureProviders.remove(key);
  293.     }

  294.     private static String getKey(String elementName, String namespace) {
  295.         return XmppStringUtils.generateKey(elementName, namespace);
  296.     }

  297.     private static void validate(String elementName, String namespace) {
  298.         if (StringUtils.isNullOrEmpty(elementName)) {
  299.             throw new IllegalArgumentException("elementName must not be null or empty");
  300.         }
  301.         if (StringUtils.isNullOrEmpty(namespace)) {
  302.             throw new IllegalArgumentException("namespace must not be null or empty");
  303.         }
  304.     }
  305. }