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(/packet) XML. For example, an XMPP time stanza(/packet) 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(/packet) 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(/packet) 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(/packet) 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(/packet) 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(/packet) 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 { 198 validate(elementName, namespace); 199 // First remove existing providers 200 String key = removeIQProvider(elementName, namespace); 201 if (provider instanceof IQProvider) { 202 iqProviders.put(key, (IQProvider<IQ>) provider); 203 } else { 204 throw new IllegalArgumentException("Provider must be an IQProvider"); 205 } 206 } 207 208 /** 209 * Removes an IQ provider with the specified element name and namespace. This 210 * method is typically called to cleanup providers that are programmatically added 211 * using the {@link #addIQProvider(String, String, Object) addIQProvider} method. 212 * 213 * @param elementName the XML element name. 214 * @param namespace the XML namespace. 215 * @return the key of the removed IQ Provider 216 */ 217 public static String removeIQProvider(String elementName, String namespace) { 218 String key = getKey(elementName, namespace); 219 iqProviders.remove(key); 220 return key; 221 } 222 223 /** 224 * Returns the stanza(/packet) extension provider registered to the specified XML element name 225 * and namespace. For example, if a provider was registered to the element name "x" and the 226 * namespace "jabber:x:event", then the following stanza(/packet) would trigger the provider: 227 * 228 * <pre> 229 * <message to='romeo@montague.net' id='message_1'> 230 * <body>Art thou not Romeo, and a Montague?</body> 231 * <x xmlns='jabber:x:event'> 232 * <composing/> 233 * </x> 234 * </message></pre> 235 * 236 * <p>Note: this method is generally only called by the internal Smack classes. 237 * 238 * @param elementName element name associated with extension provider. 239 * @param namespace namespace associated with extension provider. 240 * @return the extension provider. 241 */ 242 public static ExtensionElementProvider<ExtensionElement> getExtensionProvider(String elementName, String namespace) { 243 String key = getKey(elementName, namespace); 244 return extensionProviders.get(key); 245 } 246 247 /** 248 * Adds an extension provider with the specified element name and name space. The provider 249 * will override any providers loaded through the classpath. The provider must be either 250 * a PacketExtensionProvider instance, or a Class object of a Javabean. 251 * 252 * @param elementName the XML element name. 253 * @param namespace the XML namespace. 254 * @param provider the extension provider. 255 */ 256 @SuppressWarnings("unchecked") 257 public static void addExtensionProvider(String elementName, String namespace, 258 Object provider) 259 { 260 validate(elementName, namespace); 261 // First remove existing providers 262 String key = removeExtensionProvider(elementName, namespace); 263 if (provider instanceof ExtensionElementProvider) { 264 extensionProviders.put(key, (ExtensionElementProvider<ExtensionElement>) provider); 265 } else { 266 throw new IllegalArgumentException("Provider must be a PacketExtensionProvider"); 267 } 268 } 269 270 /** 271 * Removes an extension provider with the specified element name and namespace. This 272 * method is typically called to cleanup providers that are programmatically added 273 * using the {@link #addExtensionProvider(String, String, Object) addExtensionProvider} method. 274 * 275 * @param elementName the XML element name. 276 * @param namespace the XML namespace. 277 * @return the key of the removed stanza(/packet) extension provider 278 */ 279 public static String removeExtensionProvider(String elementName, String namespace) { 280 String key = getKey(elementName, namespace); 281 extensionProviders.remove(key); 282 return key; 283 } 284 285 /** 286 * Returns an unmodifiable collection of all PacketExtensionProvider instances. Each object 287 * in the collection will either be a PacketExtensionProvider instance, or a Class object 288 * that implements the PacketExtensionProvider interface. 289 * 290 * @return all PacketExtensionProvider instances. 291 */ 292 public static List<ExtensionElementProvider<ExtensionElement>> getExtensionProviders() { 293 List<ExtensionElementProvider<ExtensionElement>> providers = new ArrayList<>(extensionProviders.size()); 294 providers.addAll(extensionProviders.values()); 295 return providers; 296 } 297 298 public static ExtensionElementProvider<ExtensionElement> getStreamFeatureProvider(String elementName, String namespace) { 299 String key = getKey(elementName, namespace); 300 return streamFeatureProviders.get(key); 301 } 302 303 public static void addStreamFeatureProvider(String elementName, String namespace, ExtensionElementProvider<ExtensionElement> provider) { 304 validate(elementName, namespace); 305 String key = getKey(elementName, namespace); 306 streamFeatureProviders.put(key, provider); 307 } 308 309 public static void removeStreamFeatureProvider(String elementName, String namespace) { 310 String key = getKey(elementName, namespace); 311 streamFeatureProviders.remove(key); 312 } 313 314 private static String getKey(String elementName, String namespace) { 315 return XmppStringUtils.generateKey(elementName, namespace); 316 } 317 318 private static void validate(String elementName, String namespace) { 319 if (StringUtils.isNullOrEmpty(elementName)) { 320 throw new IllegalArgumentException("elementName must not be null or empty"); 321 } 322 if (StringUtils.isNullOrEmpty(namespace)) { 323 throw new IllegalArgumentException("namespace must not be null or empty"); 324 } 325 } 326}