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; 019 020import java.io.BufferedReader; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.InputStreamReader; 024import java.lang.reflect.Field; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Set; 031import java.util.logging.Level; 032import java.util.logging.Logger; 033 034import javax.net.ssl.HostnameVerifier; 035 036import org.jivesoftware.smack.compression.Java7ZlibInputOutputStream; 037import org.jivesoftware.smack.compression.XMPPInputOutputStream; 038import org.jivesoftware.smack.initializer.SmackInitializer; 039import org.jivesoftware.smack.parsing.ExceptionThrowingCallback; 040import org.jivesoftware.smack.parsing.ParsingExceptionCallback; 041import org.jivesoftware.smack.util.DNSUtil; 042import org.jivesoftware.smack.util.FileUtils; 043import org.xmlpull.v1.XmlPullParserFactory; 044import org.xmlpull.v1.XmlPullParser; 045import org.xmlpull.v1.XmlPullParserException; 046 047/** 048 * Represents the configuration of Smack. The configuration is used for: 049 * <ul> 050 * <li> Initializing classes by loading them at start-up. 051 * <li> Getting the current Smack version. 052 * <li> Getting and setting global library behavior, such as the period of time 053 * to wait for replies to packets from the server. Note: setting these values 054 * via the API will override settings in the configuration file. 055 * </ul> 056 * 057 * Configuration settings are stored in org.jivesoftware.smack/smack-config.xml. 058 * 059 * @author Gaston Dombiak 060 */ 061public final class SmackConfiguration { 062 private static final String SMACK_VERSION; 063 private static final String DEFAULT_CONFIG_FILE = "classpath:org.jivesoftware.smack/smack-config.xml"; 064 065 private static final Logger LOGGER = Logger.getLogger(SmackConfiguration.class.getName()); 066 067 private static int defaultPacketReplyTimeout = 5000; 068 private static int packetCollectorSize = 5000; 069 070 private static List<String> defaultMechs = new ArrayList<String>(); 071 072 private static Set<String> disabledSmackClasses = new HashSet<String>(); 073 074 private final static List<XMPPInputOutputStream> compressionHandlers = new ArrayList<XMPPInputOutputStream>(2); 075 076 /** 077 * Value that indicates whether debugging is enabled. When enabled, a debug 078 * window will apear for each new connection that will contain the following 079 * information:<ul> 080 * <li> Client Traffic -- raw XML traffic generated by Smack and sent to the server. 081 * <li> Server Traffic -- raw XML traffic sent by the server to the client. 082 * <li> Interpreted Packets -- shows XML packets from the server as parsed by Smack. 083 * </ul> 084 * <p/> 085 * Debugging can be enabled by setting this field to true, or by setting the Java system 086 * property <tt>smack.debugEnabled</tt> to true. The system property can be set on the 087 * command line such as "java SomeApp -Dsmack.debugEnabled=true". 088 */ 089 public static boolean DEBUG_ENABLED = false; 090 091 /** 092 * Loads the configuration from the smack-config.xml and system properties file. 093 * <p> 094 * So far this means that: 095 * 1) a set of classes will be loaded in order to execute their static init block 096 * 2) retrieve and set the current Smack release 097 * 3) set DEBUG_ENABLED 098 */ 099 static { 100 String smackVersion; 101 try { 102 BufferedReader reader = new BufferedReader(new InputStreamReader(FileUtils.getStreamForUrl("classpath:org.jivesoftware.smack/version", null))); 103 smackVersion = reader.readLine(); 104 try { 105 reader.close(); 106 } catch (IOException e) { 107 LOGGER.log(Level.WARNING, "IOException closing stream", e); 108 } 109 } catch(Exception e) { 110 LOGGER.log(Level.SEVERE, "Could not determine Smack version", e); 111 smackVersion = "unkown"; 112 } 113 SMACK_VERSION = smackVersion; 114 115 String disabledClasses = System.getProperty("smack.disabledClasses"); 116 if (disabledClasses != null) { 117 String[] splitDisabledClasses = disabledClasses.split(","); 118 for (String s : splitDisabledClasses) disabledSmackClasses.add(s); 119 } 120 try { 121 FileUtils.addLines("classpath:org.jivesoftware.smack/disabledClasses", disabledSmackClasses); 122 } 123 catch (Exception e) { 124 throw new IllegalStateException(e); 125 } 126 127 try { 128 Class<?> c = Class.forName("org.jivesoftware.smack.CustomSmackConfiguration"); 129 Field f = c.getField("DISABLED_SMACK_CLASSES"); 130 String[] sa = (String[]) f.get(null); 131 if (sa != null) 132 for (String s : sa) 133 disabledSmackClasses.add(s); 134 } 135 catch (ClassNotFoundException e1) { 136 } 137 catch (NoSuchFieldException e) { 138 } 139 catch (SecurityException e) { 140 } 141 catch (IllegalArgumentException e) { 142 } 143 catch (IllegalAccessException e) { 144 } 145 146 InputStream configFileStream; 147 try { 148 configFileStream = FileUtils.getStreamForUrl(DEFAULT_CONFIG_FILE, null); 149 } 150 catch (Exception e) { 151 throw new IllegalStateException(e); 152 } 153 154 try { 155 processConfigFile(configFileStream, null); 156 } 157 catch (Exception e) { 158 throw new IllegalStateException(e); 159 } 160 161 // Add the Java7 compression handler first, since it's preferred 162 compressionHandlers.add(new Java7ZlibInputOutputStream()); 163 164 // Use try block since we may not have permission to get a system 165 // property (for example, when an applet). 166 try { 167 DEBUG_ENABLED = Boolean.getBoolean("smack.debugEnabled"); 168 } 169 catch (Exception e) { 170 // Ignore. 171 } 172 173 // Initialize the DNS resolvers 174 DNSUtil.init(); 175 } 176 177 /** 178 * The default parsing exception callback is {@link ExceptionThrowingCallback} which will 179 * throw an exception and therefore disconnect the active connection. 180 */ 181 private static ParsingExceptionCallback defaultCallback = new ExceptionThrowingCallback(); 182 183 private static HostnameVerifier defaultHostnameVerififer; 184 185 /** 186 * Returns the Smack version information, eg "1.3.0". 187 * 188 * @return the Smack version information. 189 */ 190 public static String getVersion() { 191 return SMACK_VERSION; 192 } 193 194 /** 195 * Returns the number of milliseconds to wait for a response from 196 * the server. The default value is 5000 ms. 197 * 198 * @return the milliseconds to wait for a response from the server 199 */ 200 public static int getDefaultPacketReplyTimeout() { 201 // The timeout value must be greater than 0 otherwise we will answer the default value 202 if (defaultPacketReplyTimeout <= 0) { 203 defaultPacketReplyTimeout = 5000; 204 } 205 return defaultPacketReplyTimeout; 206 } 207 208 /** 209 * Sets the number of milliseconds to wait for a response from 210 * the server. 211 * 212 * @param timeout the milliseconds to wait for a response from the server 213 */ 214 public static void setDefaultPacketReplyTimeout(int timeout) { 215 if (timeout <= 0) { 216 throw new IllegalArgumentException(); 217 } 218 defaultPacketReplyTimeout = timeout; 219 } 220 221 /** 222 * Gets the default max size of a packet collector before it will delete 223 * the older packets. 224 * 225 * @return The number of packets to queue before deleting older packets. 226 */ 227 public static int getPacketCollectorSize() { 228 return packetCollectorSize; 229 } 230 231 /** 232 * Sets the default max size of a packet collector before it will delete 233 * the older packets. 234 * 235 * @param collectorSize the number of packets to queue before deleting older packets. 236 */ 237 public static void setPacketCollectorSize(int collectorSize) { 238 packetCollectorSize = collectorSize; 239 } 240 241 /** 242 * Add a SASL mechanism to the list to be used. 243 * 244 * @param mech the SASL mechanism to be added 245 */ 246 public static void addSaslMech(String mech) { 247 if(! defaultMechs.contains(mech) ) { 248 defaultMechs.add(mech); 249 } 250 } 251 252 /** 253 * Add a Collection of SASL mechanisms to the list to be used. 254 * 255 * @param mechs the Collection of SASL mechanisms to be added 256 */ 257 public static void addSaslMechs(Collection<String> mechs) { 258 for(String mech : mechs) { 259 addSaslMech(mech); 260 } 261 } 262 263 /** 264 * Remove a SASL mechanism from the list to be used. 265 * 266 * @param mech the SASL mechanism to be removed 267 */ 268 public static void removeSaslMech(String mech) { 269 defaultMechs.remove(mech); 270 } 271 272 /** 273 * Remove a Collection of SASL mechanisms to the list to be used. 274 * 275 * @param mechs the Collection of SASL mechanisms to be removed 276 */ 277 public static void removeSaslMechs(Collection<String> mechs) { 278 defaultMechs.removeAll(mechs); 279 } 280 281 /** 282 * Returns the list of SASL mechanisms to be used. If a SASL mechanism is 283 * listed here it does not guarantee it will be used. The server may not 284 * support it, or it may not be implemented. 285 * 286 * @return the list of SASL mechanisms to be used. 287 */ 288 public static List<String> getSaslMechs() { 289 return Collections.unmodifiableList(defaultMechs); 290 } 291 292 /** 293 * Set the default parsing exception callback for all newly created connections 294 * 295 * @param callback 296 * @see ParsingExceptionCallback 297 */ 298 public static void setDefaultParsingExceptionCallback(ParsingExceptionCallback callback) { 299 defaultCallback = callback; 300 } 301 302 /** 303 * Returns the default parsing exception callback 304 * 305 * @return the default parsing exception callback 306 * @see ParsingExceptionCallback 307 */ 308 public static ParsingExceptionCallback getDefaultParsingExceptionCallback() { 309 return defaultCallback; 310 } 311 312 public static void addCompressionHandler(XMPPInputOutputStream xmppInputOutputStream) { 313 compressionHandlers.add(xmppInputOutputStream); 314 } 315 316 public static List<XMPPInputOutputStream> getCompresionHandlers() { 317 List<XMPPInputOutputStream> res = new ArrayList<XMPPInputOutputStream>(compressionHandlers.size()); 318 for (XMPPInputOutputStream ios : compressionHandlers) { 319 if (ios.isSupported()) { 320 res.add(ios); 321 } 322 } 323 return res; 324 } 325 326 /** 327 * Set the default HostnameVerifier that will be used by XMPP connections to verify the hostname 328 * of a TLS certificate. XMPP connections are able to overwrite this settings by supplying a 329 * HostnameVerifier in their ConnecitonConfiguration with 330 * {@link ConnectionConfiguration#setHostnameVerifier(HostnameVerifier)}. 331 */ 332 public static void setDefaultHostnameVerifier(HostnameVerifier verifier) { 333 defaultHostnameVerififer = verifier; 334 } 335 336 /** 337 * Get the default HostnameVerifier 338 * 339 * @return the default HostnameVerifier or <code>null</code> if none was set 340 */ 341 static HostnameVerifier getDefaultHostnameVerifier() { 342 return defaultHostnameVerififer; 343 } 344 345 public static void processConfigFile(InputStream cfgFileStream, 346 Collection<Exception> exceptions) throws Exception { 347 processConfigFile(cfgFileStream, exceptions, SmackConfiguration.class.getClassLoader()); 348 } 349 350 public static void processConfigFile(InputStream cfgFileStream, 351 Collection<Exception> exceptions, ClassLoader classLoader) throws Exception { 352 XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); 353 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); 354 parser.setInput(cfgFileStream, "UTF-8"); 355 int eventType = parser.getEventType(); 356 do { 357 if (eventType == XmlPullParser.START_TAG) { 358 if (parser.getName().equals("startupClasses")) { 359 parseClassesToLoad(parser, false, exceptions, classLoader); 360 } 361 else if (parser.getName().equals("optionalStartupClasses")) { 362 parseClassesToLoad(parser, true, exceptions, classLoader); 363 } 364 } 365 eventType = parser.next(); 366 } 367 while (eventType != XmlPullParser.END_DOCUMENT); 368 try { 369 cfgFileStream.close(); 370 } 371 catch (IOException e) { 372 LOGGER.log(Level.SEVERE, "Error while closing config file input stream", e); 373 } 374 } 375 376 private static void parseClassesToLoad(XmlPullParser parser, boolean optional, 377 Collection<Exception> exceptions, ClassLoader classLoader) 378 throws XmlPullParserException, IOException, Exception { 379 final String startName = parser.getName(); 380 int eventType; 381 String name; 382 do { 383 eventType = parser.next(); 384 name = parser.getName(); 385 if (eventType == XmlPullParser.START_TAG && "className".equals(name)) { 386 String classToLoad = parser.nextText(); 387 if (disabledSmackClasses.contains(classToLoad)) { 388 LOGGER.info("Not loading disabled Smack class " + classToLoad); 389 } 390 else { 391 try { 392 loadSmackClass(classToLoad, optional, classLoader); 393 } 394 catch (Exception e) { 395 // Don't throw the exception if an exceptions collection is given, instead 396 // record it there. This is used for unit testing purposes. 397 if (exceptions != null) { 398 exceptions.add(e); 399 } 400 else { 401 throw e; 402 } 403 } 404 } 405 } 406 } 407 while (!(eventType == XmlPullParser.END_TAG && startName.equals(name))); 408 } 409 410 private static void loadSmackClass(String className, boolean optional, ClassLoader classLoader) throws Exception { 411 Class<?> initClass; 412 try { 413 // Attempt to load and initialize the class so that all static initializer blocks of 414 // class are executed 415 initClass = Class.forName(className, true, classLoader); 416 } 417 catch (ClassNotFoundException cnfe) { 418 Level logLevel; 419 if (optional) { 420 logLevel = Level.FINE; 421 } 422 else { 423 logLevel = Level.WARNING; 424 } 425 LOGGER.log(logLevel, "A startup class '" + className + "' could not be loaded."); 426 if (!optional) { 427 throw cnfe; 428 } else { 429 return; 430 } 431 } 432 if (SmackInitializer.class.isAssignableFrom(initClass)) { 433 SmackInitializer initializer = (SmackInitializer) initClass.newInstance(); 434 List<Exception> exceptions = initializer.initialize(); 435 if (exceptions.size() == 0) { 436 LOGGER.log(Level.FINE, "Loaded SmackInitializer " + className); 437 } else { 438 for (Exception e : exceptions) { 439 LOGGER.log(Level.SEVERE, "Exception in loadSmackClass", e); 440 } 441 } 442 } else { 443 LOGGER.log(Level.FINE, "Loaded " + className); 444 } 445 } 446}