/** * * Copyright 2003-2007 Jive Software. * * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smack.provider; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.jivesoftware.smack.packet.IQ; /** * Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of * providers exist: * * IQProvider

* * By default, Smack only knows how to process IQ packets with sub-packets that * are in a few namespaces such as:

* * Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing * mechanism is provided. IQ providers are registered programatically or by creating a * smack.providers file in the META-INF directory of your JAR file. The file is an XML * document that contains one or more iqProvider entries, as in the following example: * *
 * <?xml version="1.0"?>
 * <smackProviders>
 *     <iqProvider>
 *         <elementName>query</elementName>
 *         <namespace>jabber:iq:time</namespace>
 *         <className>org.jivesoftware.smack.packet.Time</className>
 *     </iqProvider>
 * </smackProviders>
* * Each IQ provider is associated with an element name and a namespace. If multiple provider * entries attempt to register to handle the same namespace, the first entry loaded from the * classpath will take precedence. The IQ provider class can either implement the IQProvider * interface, or extend the IQ class. In the former case, each IQProvider is responsible for * parsing the raw XML stream to create an IQ instance. In the latter case, bean introspection * is used to try to automatically set properties of the IQ instance using the values found * in the IQ packet XML. For example, an XMPP time packet resembles the following: *
 * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'>
 *     <query xmlns='jabber:iq:time'>
 *         <utc>20020910T17:58:35</utc>
 *         <tz>MDT</tz>
 *         <display>Tue Sep 10 12:58:35 2002</display>
 *     </query>
 * </iq>
* * In order for this packet to be automatically mapped to the Time object listed in the * providers file above, it must have the methods setUtc(String), setTz(String), and * setDisplay(String). The introspection service will automatically try to convert the String * value from the XML into a boolean, int, long, float, double, or Class depending on the * type the IQ instance expects.

* * A pluggable system for packet extensions, child elements in a custom namespace for * message and presence packets, also exists. Each extension provider * is registered with a name space in the smack.providers file as in the following example: * *

 * <?xml version="1.0"?>
 * <smackProviders>
 *     <extensionProvider>
 *         <elementName>x</elementName>
 *         <namespace>jabber:iq:event</namespace>
 *         <className>org.jivesoftware.smack.packet.MessageEvent</className>
 *     </extensionProvider>
 * </smackProviders>
* * If multiple provider entries attempt to register to handle the same element name and namespace, * the first entry loaded from the classpath will take precedence. Whenever a packet extension * is found in a packet, parsing will be passed to the correct provider. Each provider * can either implement the PacketExtensionProvider interface or be a standard Java Bean. In * the former case, each extension provider is responsible for parsing the raw XML stream to * contruct an object. In the latter case, bean introspection is used to try to automatically * set the properties of th class using the values in the packet extension sub-element. When an * extension provider is not registered for an element name and namespace combination, Smack will * store all top-level elements of the sub-packet in DefaultPacketExtension object and then * attach it to the packet.

* * @author Matt Tucker */ public final class ProviderManager { private static ProviderManager instance; private Map extensionProviders = new ConcurrentHashMap(); private Map iqProviders = new ConcurrentHashMap(); /** * Returns the ProviderManager instance. * * @return the only ProviderManager valid instance. */ public static synchronized ProviderManager getInstance() { if (instance == null) { instance = new ProviderManager(); } return instance; } private ProviderManager() { super(); } public void addLoader(ProviderLoader loader) { if (loader == null) { throw new IllegalArgumentException("loader cannot be null"); } if (loader.getIQProviderInfo() != null) { for (IQProviderInfo info : loader.getIQProviderInfo()) { iqProviders.put(getProviderKey(info.getElementName(), info.getNamespace()), info.getProvider()); } } if (loader.getExtensionProviderInfo() != null) { for (ExtensionProviderInfo info : loader.getExtensionProviderInfo()) { extensionProviders.put(getProviderKey(info.getElementName(), info.getNamespace()), info.getProvider()); } } } /** * Returns the IQ provider registered to the specified XML element name and namespace. * For example, if a provider was registered to the element name "query" and the * namespace "jabber:iq:time", then the following packet would trigger the provider: * *

     * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'>
     *     <query xmlns='jabber:iq:time'>
     *         <utc>20020910T17:58:35</utc>
     *         <tz>MDT</tz>
     *         <display>Tue Sep 10 12:58:35 2002</display>
     *     </query>
     * </iq>
* *

Note: this method is generally only called by the internal Smack classes. * * @param elementName the XML element name. * @param namespace the XML namespace. * @return the IQ provider. */ public Object getIQProvider(String elementName, String namespace) { String key = getProviderKey(elementName, namespace); return iqProviders.get(key); } /** * Returns an unmodifiable collection of all IQProvider instances. Each object * in the collection will either be an IQProvider instance, or a Class object * that implements the IQProvider interface. * * @return all IQProvider instances. */ public Collection getIQProviders() { return Collections.unmodifiableCollection(iqProviders.values()); } /** * Adds an IQ provider (must be an instance of IQProvider or Class object that is an IQ) * with the specified element name and name space. The provider will override any providers * loaded through the classpath. * * @param elementName the XML element name. * @param namespace the XML namespace. * @param provider the IQ provider. */ public void addIQProvider(String elementName, String namespace, Object provider) { if (!(provider instanceof IQProvider || (provider instanceof Class && IQ.class.isAssignableFrom((Class)provider)))) { throw new IllegalArgumentException("Provider must be an IQProvider " + "or a Class instance."); } String key = getProviderKey(elementName, namespace); iqProviders.put(key, provider); } /** * Removes an IQ provider with the specified element name and namespace. This * method is typically called to cleanup providers that are programatically added * using the {@link #addIQProvider(String, String, Object) addIQProvider} method. * * @param elementName the XML element name. * @param namespace the XML namespace. */ public void removeIQProvider(String elementName, String namespace) { String key = getProviderKey(elementName, namespace); iqProviders.remove(key); } /** * Returns the packet extension provider registered to the specified XML element name * and namespace. For example, if a provider was registered to the element name "x" and the * namespace "jabber:x:event", then the following packet would trigger the provider: * *
     * <message to='romeo@montague.net' id='message_1'>
     *     <body>Art thou not Romeo, and a Montague?</body>
     *     <x xmlns='jabber:x:event'>
     *         <composing/>
     *     </x>
     * </message>
* *

Note: this method is generally only called by the internal Smack classes. * * @param elementName element name associated with extension provider. * @param namespace namespace associated with extension provider. * @return the extenion provider. */ public Object getExtensionProvider(String elementName, String namespace) { String key = getProviderKey(elementName, namespace); return extensionProviders.get(key); } /** * Adds an extension provider with the specified element name and name space. The provider * will override any providers loaded through the classpath. The provider must be either * a PacketExtensionProvider instance, or a Class object of a Javabean. * * @param elementName the XML element name. * @param namespace the XML namespace. * @param provider the extension provider. */ public void addExtensionProvider(String elementName, String namespace, Object provider) { if (!(provider instanceof PacketExtensionProvider || provider instanceof Class)) { throw new IllegalArgumentException("Provider must be a PacketExtensionProvider " + "or a Class instance."); } String key = getProviderKey(elementName, namespace); extensionProviders.put(key, provider); } /** * Removes an extension provider with the specified element name and namespace. This * method is typically called to cleanup providers that are programatically added * using the {@link #addExtensionProvider(String, String, Object) addExtensionProvider} method. * * @param elementName the XML element name. * @param namespace the XML namespace. */ public void removeExtensionProvider(String elementName, String namespace) { String key = getProviderKey(elementName, namespace); extensionProviders.remove(key); } /** * Returns an unmodifiable collection of all PacketExtensionProvider instances. Each object * in the collection will either be a PacketExtensionProvider instance, or a Class object * that implements the PacketExtensionProvider interface. * * @return all PacketExtensionProvider instances. */ public Collection getExtensionProviders() { return Collections.unmodifiableCollection(extensionProviders.values()); } /** * Returns a String key for a given element name and namespace. * * @param elementName the element name. * @param namespace the namespace. * @return a unique key for the element name and namespace pair. */ private String getProviderKey(String elementName, String namespace) { StringBuilder buf = new StringBuilder(); buf.append("<").append(elementName).append("/><").append(namespace).append("/>"); return buf.toString(); } }