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