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.ArrayList; 021import java.util.List; 022import java.util.Map; 023import java.util.concurrent.ConcurrentHashMap; 024 025import org.jivesoftware.smack.SmackConfiguration; 026import org.jivesoftware.smack.packet.ExtensionElement; 027import org.jivesoftware.smack.packet.IQ; 028import org.jivesoftware.smack.util.StringUtils; 029 030import org.jxmpp.util.XmppStringUtils; 031 032/** 033 * Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of 034 * providers exist:<ul> 035 * <li>IQProvider -- parses IQ requests into Java objects. 036 * <li>PacketExtension -- parses XML sub-documents attached to packets into 037 * PacketExtension instances.</ul> 038 * 039 * <b>IQProvider</b><p> 040 * 041 * By default, Smack only knows how to process IQ packets with sub-packets that 042 * are in a few namespaces such as:<ul> 043 * <li>jabber:iq:auth 044 * <li>jabber:iq:roster 045 * <li>jabber:iq:register</ul> 046 * 047 * Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing 048 * mechanism is provided. IQ providers are registered programmatically or by creating a 049 * providers file. The file is an XML 050 * document that contains one or more iqProvider entries, as in the following example: 051 * 052 * <pre> 053 * <?xml version="1.0"?> 054 * <smackProviders> 055 * <iqProvider> 056 * <elementName>query</elementName> 057 * <namespace>jabber:iq:time</namespace> 058 * <className>org.jivesoftware.smack.packet.Time</className> 059 * </iqProvider> 060 * </smackProviders></pre> 061 * 062 * Each IQ provider is associated with an element name and a namespace. If multiple provider 063 * entries attempt to register to handle the same namespace, the first entry loaded from the 064 * classpath will take precedence. The IQ provider class can either implement the IQProvider 065 * interface, or extend the IQ class. In the former case, each IQProvider is responsible for 066 * parsing the raw XML stream to create an IQ instance. In the latter case, bean introspection 067 * is used to try to automatically set properties of the IQ instance using the values found 068 * in the IQ stanza XML. For example, an XMPP time stanza resembles the following: 069 * <pre> 070 * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'> 071 * <query xmlns='jabber:iq:time'> 072 * <utc>20020910T17:58:35</utc> 073 * <tz>MDT</tz> 074 * <display>Tue Sep 10 12:58:35 2002</display> 075 * </query> 076 * </iq></pre> 077 * 078 * In order for this stanza to be automatically mapped to the Time object listed in the 079 * providers file above, it must have the methods setUtc(String), setTz(String), and 080 * setDisplay(String). The introspection service will automatically try to convert the String 081 * value from the XML into a boolean, int, long, float, double, or Class depending on the 082 * type the IQ instance expects.<p> 083 * 084 * A pluggable system for stanza extensions, child elements in a custom namespace for 085 * message and presence packets, also exists. Each extension provider 086 * is registered with a name space in the smack.providers file as in the following example: 087 * 088 * <pre> 089 * <?xml version="1.0"?> 090 * <smackProviders> 091 * <extensionProvider> 092 * <elementName>x</elementName> 093 * <namespace>jabber:iq:event</namespace> 094 * <className>org.jivesoftware.smack.packet.MessageEvent</className> 095 * </extensionProvider> 096 * </smackProviders></pre> 097 * 098 * If multiple provider entries attempt to register to handle the same element name and namespace, 099 * the first entry loaded from the classpath will take precedence. Whenever a stanza extension 100 * is found in a packet, parsing will be passed to the correct provider. Each provider 101 * can either implement the PacketExtensionProvider interface or be a standard Java Bean. In 102 * the former case, each extension provider is responsible for parsing the raw XML stream to 103 * construct an object. In the latter case, bean introspection is used to try to automatically 104 * set the properties of th class using the values in the stanza extension sub-element. When an 105 * extension provider is not registered for an element name and namespace combination, Smack will 106 * store all top-level elements of the sub-packet in DefaultPacketExtension object and then 107 * attach it to the packet.<p> 108 * 109 * @author Matt Tucker 110 */ 111public final class ProviderManager { 112 113 private static final Map<String, ExtensionElementProvider<ExtensionElement>> extensionProviders = new ConcurrentHashMap<String, ExtensionElementProvider<ExtensionElement>>(); 114 private static final Map<String, IQProvider<IQ>> iqProviders = new ConcurrentHashMap<String, IQProvider<IQ>>(); 115 private static final Map<String, ExtensionElementProvider<ExtensionElement>> streamFeatureProviders = new ConcurrentHashMap<String, ExtensionElementProvider<ExtensionElement>>(); 116 117 static { 118 // Ensure that Smack is initialized by calling getVersion, so that user 119 // registered providers do not get overwritten by a following Smack 120 // initialization. This guarantees that Smack is initialized before a 121 // new provider is registered 122 SmackConfiguration.getVersion(); 123 } 124 125 @SuppressWarnings("unchecked") 126 public static void addLoader(ProviderLoader loader) { 127 if (loader.getIQProviderInfo() != null) { 128 for (IQProviderInfo info : loader.getIQProviderInfo()) { 129 addIQProvider(info.getElementName(), info.getNamespace(), info.getProvider()); 130 } 131 } 132 133 if (loader.getExtensionProviderInfo() != null) { 134 for (ExtensionProviderInfo info : loader.getExtensionProviderInfo()) { 135 addExtensionProvider(info.getElementName(), info.getNamespace(), info.getProvider()); 136 } 137 } 138 139 if (loader.getStreamFeatureProviderInfo() != null) { 140 for (StreamFeatureProviderInfo info : loader.getStreamFeatureProviderInfo()) { 141 addStreamFeatureProvider(info.getElementName(), info.getNamespace(), 142 (ExtensionElementProvider<ExtensionElement>) info.getProvider()); 143 } 144 } 145 } 146 147 /** 148 * Returns the IQ provider registered to the specified XML element name and namespace. 149 * For example, if a provider was registered to the element name "query" and the 150 * namespace "jabber:iq:time", then the following stanza would trigger the provider: 151 * 152 * <pre> 153 * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'> 154 * <query xmlns='jabber:iq:time'> 155 * <utc>20020910T17:58:35</utc> 156 * <tz>MDT</tz> 157 * <display>Tue Sep 10 12:58:35 2002</display> 158 * </query> 159 * </iq></pre> 160 * 161 * <p>Note: this method is generally only called by the internal Smack classes. 162 * 163 * @param elementName the XML element name. 164 * @param namespace the XML namespace. 165 * @return the IQ provider. 166 */ 167 public static IQProvider<IQ> getIQProvider(String elementName, String namespace) { 168 String key = getKey(elementName, namespace); 169 return iqProviders.get(key); 170 } 171 172 /** 173 * Returns an unmodifiable collection of all IQProvider instances. Each object 174 * in the collection will either be an IQProvider instance, or a Class object 175 * that implements the IQProvider interface. 176 * 177 * @return all IQProvider instances. 178 */ 179 public static List<IQProvider<IQ>> getIQProviders() { 180 List<IQProvider<IQ>> providers = new ArrayList<>(iqProviders.size()); 181 providers.addAll(iqProviders.values()); 182 return providers; 183 } 184 185 /** 186 * Adds an IQ provider (must be an instance of IQProvider or Class object that is an IQ) 187 * with the specified element name and name space. The provider will override any providers 188 * loaded through the classpath. 189 * 190 * @param elementName the XML element name. 191 * @param namespace the XML namespace. 192 * @param provider the IQ provider. 193 */ 194 @SuppressWarnings("unchecked") 195 public static void addIQProvider(String elementName, String namespace, 196 Object provider) { 197 validate(elementName, namespace); 198 // First remove existing providers 199 String key = removeIQProvider(elementName, namespace); 200 if (provider instanceof IQProvider) { 201 iqProviders.put(key, (IQProvider<IQ>) provider); 202 } else { 203 throw new IllegalArgumentException("Provider must be an IQProvider"); 204 } 205 } 206 207 /** 208 * Removes an IQ provider with the specified element name and namespace. This 209 * method is typically called to cleanup providers that are programmatically added 210 * using the {@link #addIQProvider(String, String, Object) addIQProvider} method. 211 * 212 * @param elementName the XML element name. 213 * @param namespace the XML namespace. 214 * @return the key of the removed IQ Provider 215 */ 216 public static String removeIQProvider(String elementName, String namespace) { 217 String key = getKey(elementName, namespace); 218 iqProviders.remove(key); 219 return key; 220 } 221 222 /** 223 * Returns the stanza extension provider registered to the specified XML element name 224 * and namespace. For example, if a provider was registered to the element name "x" and the 225 * namespace "jabber:x:event", then the following stanza would trigger the provider: 226 * 227 * <pre> 228 * <message to='romeo@montague.net' id='message_1'> 229 * <body>Art thou not Romeo, and a Montague?</body> 230 * <x xmlns='jabber:x:event'> 231 * <composing/> 232 * </x> 233 * </message></pre> 234 * 235 * <p>Note: this method is generally only called by the internal Smack classes. 236 * 237 * @param elementName element name associated with extension provider. 238 * @param namespace namespace associated with extension provider. 239 * @return the extension provider. 240 */ 241 public static ExtensionElementProvider<ExtensionElement> getExtensionProvider(String elementName, String namespace) { 242 String key = getKey(elementName, namespace); 243 return extensionProviders.get(key); 244 } 245 246 /** 247 * Adds an extension provider with the specified element name and name space. The provider 248 * will override any providers loaded through the classpath. The provider must be either 249 * a PacketExtensionProvider instance, or a Class object of a Javabean. 250 * 251 * @param elementName the XML element name. 252 * @param namespace the XML namespace. 253 * @param provider the extension provider. 254 */ 255 @SuppressWarnings("unchecked") 256 public static void addExtensionProvider(String elementName, String namespace, 257 Object provider) { 258 validate(elementName, namespace); 259 // First remove existing providers 260 String key = removeExtensionProvider(elementName, namespace); 261 if (provider instanceof ExtensionElementProvider) { 262 extensionProviders.put(key, (ExtensionElementProvider<ExtensionElement>) provider); 263 } else { 264 throw new IllegalArgumentException("Provider must be a PacketExtensionProvider"); 265 } 266 } 267 268 /** 269 * Removes an extension provider with the specified element name and namespace. This 270 * method is typically called to cleanup providers that are programmatically added 271 * using the {@link #addExtensionProvider(String, String, Object) addExtensionProvider} method. 272 * 273 * @param elementName the XML element name. 274 * @param namespace the XML namespace. 275 * @return the key of the removed stanza extension provider 276 */ 277 public static String removeExtensionProvider(String elementName, String namespace) { 278 String key = getKey(elementName, namespace); 279 extensionProviders.remove(key); 280 return key; 281 } 282 283 /** 284 * Returns an unmodifiable collection of all PacketExtensionProvider instances. Each object 285 * in the collection will either be a PacketExtensionProvider instance, or a Class object 286 * that implements the PacketExtensionProvider interface. 287 * 288 * @return all PacketExtensionProvider instances. 289 */ 290 public static List<ExtensionElementProvider<ExtensionElement>> getExtensionProviders() { 291 List<ExtensionElementProvider<ExtensionElement>> providers = new ArrayList<>(extensionProviders.size()); 292 providers.addAll(extensionProviders.values()); 293 return providers; 294 } 295 296 public static ExtensionElementProvider<ExtensionElement> getStreamFeatureProvider(String elementName, String namespace) { 297 String key = getKey(elementName, namespace); 298 return streamFeatureProviders.get(key); 299 } 300 301 public static void addStreamFeatureProvider(String elementName, String namespace, ExtensionElementProvider<ExtensionElement> provider) { 302 validate(elementName, namespace); 303 String key = getKey(elementName, namespace); 304 streamFeatureProviders.put(key, provider); 305 } 306 307 public static void removeStreamFeatureProvider(String elementName, String namespace) { 308 String key = getKey(elementName, namespace); 309 streamFeatureProviders.remove(key); 310 } 311 312 private static String getKey(String elementName, String namespace) { 313 return XmppStringUtils.generateKey(elementName, namespace); 314 } 315 316 private static void validate(String elementName, String namespace) { 317 if (StringUtils.isNullOrEmpty(elementName)) { 318 throw new IllegalArgumentException("elementName must not be null or empty"); 319 } 320 if (StringUtils.isNullOrEmpty(namespace)) { 321 throw new IllegalArgumentException("namespace must not be null or empty"); 322 } 323 } 324}