001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.jivesoftware.smack.provider; 019 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Map; 023import java.util.concurrent.ConcurrentHashMap; 024 025import org.jivesoftware.smack.packet.IQ; 026 027/** 028 * Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of 029 * providers exist:<ul> 030 * <li>IQProvider -- parses IQ requests into Java objects. 031 * <li>PacketExtension -- parses XML sub-documents attached to packets into 032 * PacketExtension instances.</ul> 033 * 034 * <b>IQProvider</b><p> 035 * 036 * By default, Smack only knows how to process IQ packets with sub-packets that 037 * are in a few namespaces such as:<ul> 038 * <li>jabber:iq:auth 039 * <li>jabber:iq:roster 040 * <li>jabber:iq:register</ul> 041 * 042 * Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing 043 * mechanism is provided. IQ providers are registered programatically or by creating a 044 * providers file. The file is an XML 045 * document that contains one or more iqProvider entries, as in the following example: 046 * 047 * <pre> 048 * <?xml version="1.0"?> 049 * <smackProviders> 050 * <iqProvider> 051 * <elementName>query</elementName> 052 * <namespace>jabber:iq:time</namespace> 053 * <className>org.jivesoftware.smack.packet.Time</className> 054 * </iqProvider> 055 * </smackProviders></pre> 056 * 057 * Each IQ provider is associated with an element name and a namespace. If multiple provider 058 * entries attempt to register to handle the same namespace, the first entry loaded from the 059 * classpath will take precedence. The IQ provider class can either implement the IQProvider 060 * interface, or extend the IQ class. In the former case, each IQProvider is responsible for 061 * parsing the raw XML stream to create an IQ instance. In the latter case, bean introspection 062 * is used to try to automatically set properties of the IQ instance using the values found 063 * in the IQ packet XML. For example, an XMPP time packet resembles the following: 064 * <pre> 065 * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'> 066 * <query xmlns='jabber:iq:time'> 067 * <utc>20020910T17:58:35</utc> 068 * <tz>MDT</tz> 069 * <display>Tue Sep 10 12:58:35 2002</display> 070 * </query> 071 * </iq></pre> 072 * 073 * In order for this packet to be automatically mapped to the Time object listed in the 074 * providers file above, it must have the methods setUtc(String), setTz(String), and 075 * setDisplay(String). The introspection service will automatically try to convert the String 076 * value from the XML into a boolean, int, long, float, double, or Class depending on the 077 * type the IQ instance expects.<p> 078 * 079 * A pluggable system for packet extensions, child elements in a custom namespace for 080 * message and presence packets, also exists. Each extension provider 081 * is registered with a name space in the smack.providers file as in the following example: 082 * 083 * <pre> 084 * <?xml version="1.0"?> 085 * <smackProviders> 086 * <extensionProvider> 087 * <elementName>x</elementName> 088 * <namespace>jabber:iq:event</namespace> 089 * <className>org.jivesoftware.smack.packet.MessageEvent</className> 090 * </extensionProvider> 091 * </smackProviders></pre> 092 * 093 * If multiple provider entries attempt to register to handle the same element name and namespace, 094 * the first entry loaded from the classpath will take precedence. Whenever a packet extension 095 * is found in a packet, parsing will be passed to the correct provider. Each provider 096 * can either implement the PacketExtensionProvider interface or be a standard Java Bean. In 097 * the former case, each extension provider is responsible for parsing the raw XML stream to 098 * contruct an object. In the latter case, bean introspection is used to try to automatically 099 * 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 */ 106public final class ProviderManager { 107 108 private static final Map<String, Object> extensionProviders = new ConcurrentHashMap<String, Object>(); 109 private static final Map<String, Object> iqProviders = new ConcurrentHashMap<String, Object>(); 110 111 public static void addLoader(ProviderLoader loader) { 112 if (loader.getIQProviderInfo() != null) { 113 for (IQProviderInfo info : loader.getIQProviderInfo()) { 114 iqProviders.put(getProviderKey(info.getElementName(), info.getNamespace()), info.getProvider()); 115 } 116 } 117 118 if (loader.getExtensionProviderInfo() != null) { 119 for (ExtensionProviderInfo info : loader.getExtensionProviderInfo()) { 120 extensionProviders.put(getProviderKey(info.getElementName(), info.getNamespace()), info.getProvider()); 121 } 122 } 123 } 124 125 /** 126 * Returns the IQ provider registered to the specified XML element name and namespace. 127 * For example, if a provider was registered to the element name "query" and the 128 * namespace "jabber:iq:time", then the following packet would trigger the provider: 129 * 130 * <pre> 131 * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'> 132 * <query xmlns='jabber:iq:time'> 133 * <utc>20020910T17:58:35</utc> 134 * <tz>MDT</tz> 135 * <display>Tue Sep 10 12:58:35 2002</display> 136 * </query> 137 * </iq></pre> 138 * 139 * <p>Note: this method is generally only called by the internal Smack classes. 140 * 141 * @param elementName the XML element name. 142 * @param namespace the XML namespace. 143 * @return the IQ provider. 144 */ 145 public static Object getIQProvider(String elementName, String namespace) { 146 String key = getProviderKey(elementName, namespace); 147 return iqProviders.get(key); 148 } 149 150 /** 151 * Returns an unmodifiable collection of all IQProvider instances. Each object 152 * in the collection will either be an IQProvider instance, or a Class object 153 * that implements the IQProvider interface. 154 * 155 * @return all IQProvider instances. 156 */ 157 public static Collection<Object> getIQProviders() { 158 return Collections.unmodifiableCollection(iqProviders.values()); 159 } 160 161 /** 162 * Adds an IQ provider (must be an instance of IQProvider or Class object that is an IQ) 163 * with the specified element name and name space. The provider will override any providers 164 * loaded through the classpath. 165 * 166 * @param elementName the XML element name. 167 * @param namespace the XML namespace. 168 * @param provider the IQ provider. 169 */ 170 public static void addIQProvider(String elementName, String namespace, 171 Object provider) 172 { 173 if (!(provider instanceof IQProvider || (provider instanceof Class && 174 IQ.class.isAssignableFrom((Class<?>)provider)))) 175 { 176 throw new IllegalArgumentException("Provider must be an IQProvider " + 177 "or a Class instance sublcassing IQ."); 178 } 179 String key = getProviderKey(elementName, namespace); 180 iqProviders.put(key, provider); 181 } 182 183 /** 184 * Removes an IQ provider with the specified element name and namespace. This 185 * method is typically called to cleanup providers that are programatically added 186 * using the {@link #addIQProvider(String, String, Object) addIQProvider} method. 187 * 188 * @param elementName the XML element name. 189 * @param namespace the XML namespace. 190 */ 191 public static void removeIQProvider(String elementName, String namespace) { 192 String key = getProviderKey(elementName, namespace); 193 iqProviders.remove(key); 194 } 195 196 /** 197 * Returns the packet extension provider registered to the specified XML element name 198 * and namespace. For example, if a provider was registered to the element name "x" and the 199 * namespace "jabber:x:event", then the following packet would trigger the provider: 200 * 201 * <pre> 202 * <message to='romeo@montague.net' id='message_1'> 203 * <body>Art thou not Romeo, and a Montague?</body> 204 * <x xmlns='jabber:x:event'> 205 * <composing/> 206 * </x> 207 * </message></pre> 208 * 209 * <p>Note: this method is generally only called by the internal Smack classes. 210 * 211 * @param elementName element name associated with extension provider. 212 * @param namespace namespace associated with extension provider. 213 * @return the extenion provider. 214 */ 215 public static Object getExtensionProvider(String elementName, String namespace) { 216 String key = getProviderKey(elementName, namespace); 217 return extensionProviders.get(key); 218 } 219 220 /** 221 * Adds an extension provider with the specified element name and name space. The provider 222 * will override any providers loaded through the classpath. The provider must be either 223 * a PacketExtensionProvider instance, or a Class object of a Javabean. 224 * 225 * @param elementName the XML element name. 226 * @param namespace the XML namespace. 227 * @param provider the extension provider. 228 */ 229 public static void addExtensionProvider(String elementName, String namespace, 230 Object provider) 231 { 232 if (!(provider instanceof PacketExtensionProvider || provider instanceof Class)) { 233 throw new IllegalArgumentException("Provider must be a PacketExtensionProvider " + 234 "or a Class instance."); 235 } 236 String key = getProviderKey(elementName, namespace); 237 extensionProviders.put(key, provider); 238 } 239 240 /** 241 * Removes an extension provider with the specified element name and namespace. This 242 * method is typically called to cleanup providers that are programatically added 243 * using the {@link #addExtensionProvider(String, String, Object) addExtensionProvider} method. 244 * 245 * @param elementName the XML element name. 246 * @param namespace the XML namespace. 247 */ 248 public static void removeExtensionProvider(String elementName, String namespace) { 249 String key = getProviderKey(elementName, namespace); 250 extensionProviders.remove(key); 251 } 252 253 /** 254 * Returns an unmodifiable collection of all PacketExtensionProvider instances. Each object 255 * in the collection will either be a PacketExtensionProvider instance, or a Class object 256 * that implements the PacketExtensionProvider interface. 257 * 258 * @return all PacketExtensionProvider instances. 259 */ 260 public static Collection<Object> getExtensionProviders() { 261 return Collections.unmodifiableCollection(extensionProviders.values()); 262 } 263 264 /** 265 * Returns a String key for a given element name and namespace. 266 * 267 * @param elementName the element name. 268 * @param namespace the namespace. 269 * @return a unique key for the element name and namespace pair. 270 */ 271 private static String getProviderKey(String elementName, String namespace) { 272 return elementName + '#' + namespace; 273 } 274}