1 /** 2 * $RCSfile$ 3 * $Revision$ 4 * $Date$ 5 * 6 * Copyright 2003-2007 Jive Software. 7 * 8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 package org.jivesoftware.smack.provider; 22 23 import org.jivesoftware.smack.packet.IQ; 24 import org.jivesoftware.smack.packet.PacketExtension; 25 import org.xmlpull.v1.XmlPullParserFactory; 26 import org.xmlpull.v1.XmlPullParser; 27 28 import java.io.InputStream; 29 import java.net.URL; 30 import java.util.*; 31 import java.util.concurrent.ConcurrentHashMap; 32 33 /** 34 * Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of 35 * providers exist:<ul> 36 * <li>IQProvider -- parses IQ requests into Java objects. 37 * <li>PacketExtension -- parses XML sub-documents attached to packets into 38 * PacketExtension instances.</ul> 39 * 40 * <b>IQProvider</b><p> 41 * 42 * By default, Smack only knows how to process IQ packets with sub-packets that 43 * are in a few namespaces such as:<ul> 44 * <li>jabber:iq:auth 45 * <li>jabber:iq:roster 46 * <li>jabber:iq:register</ul> 47 * 48 * Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing 49 * mechanism is provided. IQ providers are registered programatically or by creating a 50 * smack.providers file in the META-INF directory of your JAR file. The file is an XML 51 * document that contains one or more iqProvider entries, as in the following example: 52 * 53 * <pre> 54 * <?xml version="1.0"?> 55 * <smackProviders> 56 * <iqProvider> 57 * <elementName>query</elementName> 58 * <namespace>jabber:iq:time</namespace> 59 * <className>org.jivesoftware.smack.packet.Time</className> 60 * </iqProvider> 61 * </smackProviders></pre> 62 * 63 * Each IQ provider is associated with an element name and a namespace. If multiple provider 64 * entries attempt to register to handle the same namespace, the first entry loaded from the 65 * classpath will take precedence. The IQ provider class can either implement the IQProvider 66 * interface, or extend the IQ class. In the former case, each IQProvider is responsible for 67 * parsing the raw XML stream to create an IQ instance. In the latter case, bean introspection 68 * is used to try to automatically set properties of the IQ instance using the values found 69 * in the IQ packet XML. For example, an XMPP time packet resembles the following: 70 * <pre> 71 * <iq type='result' to='joe (at) example.com' from='mary (at) example.com' id='time_1'> 72 * <query xmlns='jabber:iq:time'> 73 * <utc>20020910T17:58:35</utc> 74 * <tz>MDT</tz> 75 * <display>Tue Sep 10 12:58:35 2002</display> 76 * </query> 77 * </iq></pre> 78 * 79 * In order for this packet to be automatically mapped to the Time object listed in the 80 * providers file above, it must have the methods setUtc(String), setTz(String), and 81 * setDisplay(String). The introspection service will automatically try to convert the String 82 * value from the XML into a boolean, int, long, float, double, or Class depending on the 83 * type the IQ instance expects.<p> 84 * 85 * A pluggable system for packet extensions, child elements in a custom namespace for 86 * message and presence packets, also exists. Each extension provider 87 * is registered with a name space in the smack.providers file as in the following example: 88 * 89 * <pre> 90 * <?xml version="1.0"?> 91 * <smackProviders> 92 * <extensionProvider> 93 * <elementName>x</elementName> 94 * <namespace>jabber:iq:event</namespace> 95 * <className>org.jivesoftware.smack.packet.MessageEvent</className> 96 * </extensionProvider> 97 * </smackProviders></pre> 98 * 99 * If multiple provider entries attempt to register to handle the same element name and namespace, 100 * the first entry loaded from the classpath will take precedence. Whenever a packet extension 101 * is found in a packet, parsing will be passed to the correct provider. Each provider 102 * can either implement the PacketExtensionProvider interface or be a standard Java Bean. In 103 * the former case, each extension provider is responsible for parsing the raw XML stream to 104 * contruct an object. In the latter case, bean introspection is used to try to automatically 105 * set the properties of the class using the values in the packet extension sub-element. When an 106 * extension provider is not registered for an element name and namespace combination, Smack will 107 * store all top-level elements of the sub-packet in DefaultPacketExtension object and then 108 * attach it to the packet.<p> 109 * 110 * It is possible to provide a custom provider manager instead of the default implementation 111 * provided by Smack. If you want to provide your own provider manager then you need to do it 112 * before creating any {@link org.jivesoftware.smack.Connection} by sending the static 113 * {@link #setInstance(ProviderManager)} message. Trying to change the provider manager after 114 * an Connection was created will result in an {@link IllegalStateException} error. 115 * 116 * @author Matt Tucker 117 */ 118 public class ProviderManager { 119 120 private static ProviderManager instance; 121 122 private Map<String, Object> extensionProviders = new ConcurrentHashMap<String, Object>(); 123 private Map<String, Object> iqProviders = new ConcurrentHashMap<String, Object>(); 124 125 /** 126 * Returns the only ProviderManager valid instance. Use {@link #setInstance(ProviderManager)} 127 * to configure your own provider manager. If non was provided then an instance of this 128 * class will be used. 129 * 130 * @return the only ProviderManager valid instance. 131 */ 132 public static synchronized ProviderManager getInstance() { 133 if (instance == null) { 134 instance = new ProviderManager(); 135 } 136 return instance; 137 } 138 139 /** 140 * Sets the only ProviderManager valid instance to be used by all Connections. If you 141 * want to provide your own provider manager then you need to do it before creating 142 * any Connection. Otherwise an IllegalStateException will be thrown. 143 * 144 * @param providerManager the only ProviderManager valid instance to be used. 145 * @throws IllegalStateException if a provider manager was already configued. 146 */ 147 public static synchronized void setInstance(ProviderManager providerManager) { 148 if (instance != null) { 149 throw new IllegalStateException("ProviderManager singleton already set"); 150 } 151 instance = providerManager; 152 } 153 154 protected void initialize() { 155 // Load IQ processing providers. 156 try { 157 // Get an array of class loaders to try loading the providers files from. 158 ClassLoader[] classLoaders = getClassLoaders(); 159 for (ClassLoader classLoader : classLoaders) { 160 Enumeration<URL> providerEnum = classLoader.getResources( 161 "META-INF/smack.providers"); 162 while (providerEnum.hasMoreElements()) { 163 URL url = providerEnum.nextElement(); 164 InputStream providerStream = null; 165 try { 166 providerStream = url.openStream(); 167 XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); 168 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); 169 parser.setInput(providerStream, "UTF-8"); 170 int eventType = parser.getEventType(); 171 do { 172 if (eventType == XmlPullParser.START_TAG) { 173 if (parser.getName().equals("iqProvider")) { 174 parser.next(); 175 parser.next(); 176 String elementName = parser.nextText(); 177 parser.next(); 178 parser.next(); 179 String namespace = parser.nextText(); 180 parser.next(); 181 parser.next(); 182 String className = parser.nextText(); 183 // Only add the provider for the namespace if one isn't 184 // already registered. 185 String key = getProviderKey(elementName, namespace); 186 if (!iqProviders.containsKey(key)) { 187 // Attempt to load the provider class and then create 188 // a new instance if it's an IQProvider. Otherwise, if it's 189 // an IQ class, add the class object itself, then we'll use 190 // reflection later to create instances of the class. 191 try { 192 // Add the provider to the map. 193 Class<?> provider = Class.forName(className); 194 if (IQProvider.class.isAssignableFrom(provider)) { 195 iqProviders.put(key, provider.newInstance()); 196 } 197 else if (IQ.class.isAssignableFrom(provider)) { 198 iqProviders.put(key, provider); 199 } 200 } 201 catch (ClassNotFoundException cnfe) { 202 cnfe.printStackTrace(); 203 } 204 } 205 } 206 else if (parser.getName().equals("extensionProvider")) { 207 parser.next(); 208 parser.next(); 209 String elementName = parser.nextText(); 210 parser.next(); 211 parser.next(); 212 String namespace = parser.nextText(); 213 parser.next(); 214 parser.next(); 215 String className = parser.nextText(); 216 // Only add the provider for the namespace if one isn't 217 // already registered. 218 String key = getProviderKey(elementName, namespace); 219 if (!extensionProviders.containsKey(key)) { 220 // Attempt to load the provider class and then create 221 // a new instance if it's a Provider. Otherwise, if it's 222 // a PacketExtension, add the class object itself and 223 // then we'll use reflection later to create instances 224 // of the class. 225 try { 226 // Add the provider to the map. 227 Class<?> provider = Class.forName(className); 228 if (PacketExtensionProvider.class.isAssignableFrom( 229 provider)) { 230 extensionProviders.put(key, provider.newInstance()); 231 } 232 else if (PacketExtension.class.isAssignableFrom( 233 provider)) { 234 extensionProviders.put(key, provider); 235 } 236 } 237 catch (ClassNotFoundException cnfe) { 238 cnfe.printStackTrace(); 239 } 240 } 241 } 242 } 243 eventType = parser.next(); 244 } 245 while (eventType != XmlPullParser.END_DOCUMENT); 246 } 247 finally { 248 try { 249 providerStream.close(); 250 } 251 catch (Exception e) { 252 // Ignore. 253 } 254 } 255 } 256 } 257 } 258 catch (Exception e) { 259 e.printStackTrace(); 260 } 261 } 262 263 /** 264 * Returns the IQ provider registered to the specified XML element name and namespace. 265 * For example, if a provider was registered to the element name "query" and the 266 * namespace "jabber:iq:time", then the following packet would trigger the provider: 267 * 268 * <pre> 269 * <iq type='result' to='joe (at) example.com' from='mary (at) example.com' id='time_1'> 270 * <query xmlns='jabber:iq:time'> 271 * <utc>20020910T17:58:35</utc> 272 * <tz>MDT</tz> 273 * <display>Tue Sep 10 12:58:35 2002</display> 274 * </query> 275 * </iq></pre> 276 * 277 * <p>Note: this method is generally only called by the internal Smack classes. 278 * 279 * @param elementName the XML element name. 280 * @param namespace the XML namespace. 281 * @return the IQ provider. 282 */ 283 public Object getIQProvider(String elementName, String namespace) { 284 String key = getProviderKey(elementName, namespace); 285 return iqProviders.get(key); 286 } 287 288 /** 289 * Returns an unmodifiable collection of all IQProvider instances. Each object 290 * in the collection will either be an IQProvider instance, or a Class object 291 * that implements the IQProvider interface. 292 * 293 * @return all IQProvider instances. 294 */ 295 public Collection<Object> getIQProviders() { 296 return Collections.unmodifiableCollection(iqProviders.values()); 297 } 298 299 /** 300 * Adds an IQ provider (must be an instance of IQProvider or Class object that is an IQ) 301 * with the specified element name and name space. The provider will override any providers 302 * loaded through the classpath. 303 * 304 * @param elementName the XML element name. 305 * @param namespace the XML namespace. 306 * @param provider the IQ provider. 307 */ 308 public void addIQProvider(String elementName, String namespace, 309 Object provider) 310 { 311 if (!(provider instanceof IQProvider || (provider instanceof Class && 312 IQ.class.isAssignableFrom((Class<?>)provider)))) 313 { 314 throw new IllegalArgumentException("Provider must be an IQProvider " + 315 "or a Class instance."); 316 } 317 String key = getProviderKey(elementName, namespace); 318 iqProviders.put(key, provider); 319 } 320 321 /** 322 * Removes an IQ provider with the specified element name and namespace. This 323 * method is typically called to cleanup providers that are programatically added 324 * using the {@link #addIQProvider(String, String, Object) addIQProvider} method. 325 * 326 * @param elementName the XML element name. 327 * @param namespace the XML namespace. 328 */ 329 public void removeIQProvider(String elementName, String namespace) { 330 String key = getProviderKey(elementName, namespace); 331 iqProviders.remove(key); 332 } 333 334 /** 335 * Returns the packet extension provider registered to the specified XML element name 336 * and namespace. For example, if a provider was registered to the element name "x" and the 337 * namespace "jabber:x:event", then the following packet would trigger the provider: 338 * 339 * <pre> 340 * <message to='romeo (at) montague.net' id='message_1'> 341 * <body>Art thou not Romeo, and a Montague?</body> 342 * <x xmlns='jabber:x:event'> 343 * <composing/> 344 * </x> 345 * </message></pre> 346 * 347 * <p>Note: this method is generally only called by the internal Smack classes. 348 * 349 * @param elementName element name associated with extension provider. 350 * @param namespace namespace associated with extension provider. 351 * @return the extenion provider. 352 */ 353 public Object getExtensionProvider(String elementName, String namespace) { 354 String key = getProviderKey(elementName, namespace); 355 return extensionProviders.get(key); 356 } 357 358 /** 359 * Adds an extension provider with the specified element name and name space. The provider 360 * will override any providers loaded through the classpath. The provider must be either 361 * a PacketExtensionProvider instance, or a Class object of a Javabean. 362 * 363 * @param elementName the XML element name. 364 * @param namespace the XML namespace. 365 * @param provider the extension provider. 366 */ 367 public void addExtensionProvider(String elementName, String namespace, 368 Object provider) 369 { 370 if (!(provider instanceof PacketExtensionProvider || provider instanceof Class)) { 371 throw new IllegalArgumentException("Provider must be a PacketExtensionProvider " + 372 "or a Class instance."); 373 } 374 String key = getProviderKey(elementName, namespace); 375 extensionProviders.put(key, provider); 376 } 377 378 /** 379 * Removes an extension provider with the specified element name and namespace. This 380 * method is typically called to cleanup providers that are programatically added 381 * using the {@link #addExtensionProvider(String, String, Object) addExtensionProvider} method. 382 * 383 * @param elementName the XML element name. 384 * @param namespace the XML namespace. 385 */ 386 public void removeExtensionProvider(String elementName, String namespace) { 387 String key = getProviderKey(elementName, namespace); 388 extensionProviders.remove(key); 389 } 390 391 /** 392 * Returns an unmodifiable collection of all PacketExtensionProvider instances. Each object 393 * in the collection will either be a PacketExtensionProvider instance, or a Class object 394 * that implements the PacketExtensionProvider interface. 395 * 396 * @return all PacketExtensionProvider instances. 397 */ 398 public Collection<Object> getExtensionProviders() { 399 return Collections.unmodifiableCollection(extensionProviders.values()); 400 } 401 402 /** 403 * Returns a String key for a given element name and namespace. 404 * 405 * @param elementName the element name. 406 * @param namespace the namespace. 407 * @return a unique key for the element name and namespace pair. 408 */ 409 private String getProviderKey(String elementName, String namespace) { 410 StringBuilder buf = new StringBuilder(); 411 buf.append("<").append(elementName).append("/><").append(namespace).append("/>"); 412 return buf.toString(); 413 } 414 415 /** 416 * Returns an array of class loaders to load resources from. 417 * 418 * @return an array of ClassLoader instances. 419 */ 420 private ClassLoader[] getClassLoaders() { 421 ClassLoader[] classLoaders = new ClassLoader[2]; 422 classLoaders[0] = ProviderManager.class.getClassLoader(); 423 classLoaders[1] = Thread.currentThread().getContextClassLoader(); 424 // Clean up possible null values. Note that #getClassLoader may return a null value. 425 List<ClassLoader> loaders = new ArrayList<ClassLoader>(); 426 for (ClassLoader classLoader : classLoaders) { 427 if (classLoader != null) { 428 loaders.add(classLoader); 429 } 430 } 431 return loaders.toArray(new ClassLoader[loaders.size()]); 432 } 433 434 private ProviderManager() { 435 super(); 436 initialize(); 437 } 438 }