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