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