/** * $RCSfile$ * $Revision: 11613 $ * $Date: 2010-02-09 05:55:56 -0600 (Tue, 09 Feb 2010) $ * * 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.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.PacketExtension; import org.xmlpull.mxp1.MXParser; import org.xmlpull.v1.XmlPullParser; /** * Manages providers for parsing custom XML sub-documents of XMPP packets. Two * types of providers exist: * <ul> * <li>IQProvider -- parses IQ requests into Java objects. * <li>PacketExtension -- parses XML sub-documents attached to packets into * PacketExtension instances. * </ul> * * <b>IQProvider</b> * <p> * * By default, Smack only knows how to process IQ packets with sub-packets that * are in a few namespaces such as: * <ul> * <li>jabber:iq:auth * <li>jabber:iq:roster * <li>jabber:iq:register * </ul> * * 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: * * <pre> * <?xml version="1.0"?> * <smackProviders> * <iqProvider> * <elementName>query</elementName> * <namespace>jabber:iq:time</namespace> * <className>org.jivesoftware.smack.packet.Time</className> * </iqProvider> * </smackProviders> * </pre> * * 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: * * <pre> * <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> * </pre> * * 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. * <p> * * 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: * * <pre> * <?xml version="1.0"?> * <smackProviders> * <extensionProvider> * <elementName>x</elementName> * <namespace>jabber:iq:event</namespace> * <className>org.jivesoftware.smack.packet.MessageEvent</className> * </extensionProvider> * </smackProviders> * </pre> * * 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 the 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. * <p> * * It is possible to provide a custom provider manager instead of the default * implementation provided by Smack. If you want to provide your own provider * manager then you need to do it before creating any * {@link org.jivesoftware.smack.Connection} by sending the static * {@link #setInstance(ProviderManager)} message. Trying to change the provider * manager after an Connection was created will result in an * {@link IllegalStateException} error. * * @author Matt Tucker */ public class ProviderManager { private static ProviderManager instance; /** * Returns the only ProviderManager valid instance. Use * {@link #setInstance(ProviderManager)} to configure your own provider * manager. If non was provided then an instance of this class will be used. * * @return the only ProviderManager valid instance. */ public static synchronized ProviderManager getInstance() { if (instance == null) { instance = new ProviderManager(); } return instance; } /** * Sets the only ProviderManager valid instance to be used by all * Connections. If you want to provide your own provider manager then you * need to do it before creating any Connection. Otherwise an * IllegalStateException will be thrown. * * @param providerManager * the only ProviderManager valid instance to be used. * @throws IllegalStateException * if a provider manager was already configued. */ public static synchronized void setInstance(ProviderManager providerManager) { if (instance != null) { throw new IllegalStateException( "ProviderManager singleton already set"); } instance = providerManager; } private final Map<String, Object> extensionProviders = new ConcurrentHashMap<String, Object>(); private final Map<String, Object> iqProviders = new ConcurrentHashMap<String, Object>(); private ProviderManager() { super(); initialize(); } /** * 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."); } final String key = getProviderKey(elementName, namespace); extensionProviders.put(key, provider); } /** * 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."); } final String key = getProviderKey(elementName, namespace); iqProviders.put(key, provider); } /** * Returns an array of class loaders to load resources from. * * @return an array of ClassLoader instances. */ private ClassLoader[] getClassLoaders() { final ClassLoader[] classLoaders = new ClassLoader[2]; classLoaders[0] = ProviderManager.class.getClassLoader(); classLoaders[1] = Thread.currentThread().getContextClassLoader(); // Clean up possible null values. Note that #getClassLoader may return a // null value. final List<ClassLoader> loaders = new ArrayList<ClassLoader>(); for (final ClassLoader classLoader : classLoaders) { if (classLoader != null) { loaders.add(classLoader); } } return loaders.toArray(new ClassLoader[loaders.size()]); } /** * 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: * * <pre> * <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> * </pre> * * <p> * 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) { final String key = getProviderKey(elementName, namespace); return extensionProviders.get(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<Object> getExtensionProviders() { return Collections.unmodifiableCollection(extensionProviders.values()); } /** * 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: * * <pre> * <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> * </pre> * * <p> * 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) { final 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<Object> getIQProviders() { return Collections.unmodifiableCollection(iqProviders.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) { final StringBuilder buf = new StringBuilder(); buf.append("<").append(elementName).append("/><").append(namespace) .append("/>"); return buf.toString(); } protected void initialize() { // Load IQ processing providers. try { // Get an array of class loaders to try loading the providers files // from. final ClassLoader[] classLoaders = getClassLoaders(); for (final ClassLoader classLoader : classLoaders) { final Enumeration<URL> providerEnum = classLoader .getResources("assets/smack.providers"); while (providerEnum.hasMoreElements()) { final URL url = providerEnum.nextElement(); InputStream providerStream = null; try { providerStream = url.openStream(); final XmlPullParser parser = new MXParser(); parser.setFeature( XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(providerStream, "UTF-8"); int eventType = parser.getEventType(); do { if (eventType == XmlPullParser.START_TAG) { if (parser.getName().equals("iqProvider")) { parser.next(); parser.next(); final String elementName = parser .nextText(); parser.next(); parser.next(); final String namespace = parser.nextText(); parser.next(); parser.next(); final String className = parser.nextText(); // Only add the provider for the namespace // if one isn't // already registered. final String key = getProviderKey( elementName, namespace); if (!iqProviders.containsKey(key)) { // Attempt to load the provider class // and then create // a new instance if it's an IQProvider. // Otherwise, if it's // an IQ class, add the class object // itself, then we'll use // reflection later to create instances // of the class. try { // Add the provider to the map. final Class<?> provider = Class .forName(className); if (IQProvider.class .isAssignableFrom(provider)) { iqProviders.put(key, provider.newInstance()); } else if (IQ.class .isAssignableFrom(provider)) { iqProviders.put(key, provider); } } catch (final ClassNotFoundException cnfe) { cnfe.printStackTrace(); } } } else if (parser.getName().equals( "extensionProvider")) { parser.next(); parser.next(); final String elementName = parser .nextText(); parser.next(); parser.next(); final String namespace = parser.nextText(); parser.next(); parser.next(); final String className = parser.nextText(); // Only add the provider for the namespace // if one isn't // already registered. final String key = getProviderKey( elementName, namespace); if (!extensionProviders.containsKey(key)) { // Attempt to load the provider class // and then create // a new instance if it's a Provider. // Otherwise, if it's // a PacketExtension, add the class // object itself and // then we'll use reflection later to // create instances // of the class. try { // Add the provider to the map. final Class<?> provider = Class .forName(className); if (PacketExtensionProvider.class .isAssignableFrom(provider)) { extensionProviders.put(key, provider.newInstance()); } else if (PacketExtension.class .isAssignableFrom(provider)) { extensionProviders.put(key, provider); } } catch (final ClassNotFoundException cnfe) { cnfe.printStackTrace(); } } } } eventType = parser.next(); } while (eventType != XmlPullParser.END_DOCUMENT); } finally { try { providerStream.close(); } catch (final Exception e) { // Ignore. } } } } } catch (final Exception e) { e.printStackTrace(); } } /** * 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) { final String key = getProviderKey(elementName, namespace); extensionProviders.remove(key); } /** * 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) { final String key = getProviderKey(elementName, namespace); iqProviders.remove(key); } }