001/** 002 * 003 * Copyright 2003-2007 Jive Software, 2014-2020 Florian Schmaus 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.InputStream; 022import java.io.InputStreamReader; 023import java.nio.charset.StandardCharsets; 024import java.util.Collection; 025import java.util.List; 026import java.util.logging.Level; 027import java.util.logging.Logger; 028 029import org.jivesoftware.smack.bind2.Bind2ModuleDescriptor; 030import org.jivesoftware.smack.compress.provider.CompressedProvider; 031import org.jivesoftware.smack.compress.provider.FailureProvider; 032import org.jivesoftware.smack.compression.CompressionModuleDescriptor; 033import org.jivesoftware.smack.compression.Java7ZlibInputOutputStream; 034import org.jivesoftware.smack.compression.XmppCompressionManager; 035import org.jivesoftware.smack.compression.zlib.ZlibXmppCompressionFactory; 036import org.jivesoftware.smack.initializer.SmackInitializer; 037import org.jivesoftware.smack.isr.InstantStreamResumptionModuleDescriptor; 038import org.jivesoftware.smack.packet.Bind; 039import org.jivesoftware.smack.packet.Message; 040import org.jivesoftware.smack.provider.BindIQProvider; 041import org.jivesoftware.smack.provider.BodyElementProvider; 042import org.jivesoftware.smack.provider.MessageSubjectElementProvider; 043import org.jivesoftware.smack.provider.MessageThreadElementProvider; 044import org.jivesoftware.smack.provider.ProviderManager; 045import org.jivesoftware.smack.provider.SaslChallengeProvider; 046import org.jivesoftware.smack.provider.SaslFailureProvider; 047import org.jivesoftware.smack.provider.SaslSuccessProvider; 048import org.jivesoftware.smack.provider.TlsFailureProvider; 049import org.jivesoftware.smack.provider.TlsProceedProvider; 050import org.jivesoftware.smack.sasl.core.SASLAnonymous; 051import org.jivesoftware.smack.sasl.core.SASLXOauth2Mechanism; 052import org.jivesoftware.smack.sasl.core.SCRAMSHA1Mechanism; 053import org.jivesoftware.smack.sasl.core.ScramSha1PlusMechanism; 054import org.jivesoftware.smack.util.CloseableUtil; 055import org.jivesoftware.smack.util.FileUtils; 056import org.jivesoftware.smack.util.PacketParserUtils; 057import org.jivesoftware.smack.xml.XmlPullParser; 058 059public final class SmackInitialization { 060 static final String SMACK_VERSION; 061 062 private static final String DEFAULT_CONFIG_FILE = "org.jivesoftware.smack/smack-config.xml"; 063 064 private static final Logger LOGGER = Logger.getLogger(SmackInitialization.class.getName()); 065 066 /** 067 * Loads the configuration from the smack-config.xml and system properties file. 068 * <p> 069 * So far this means that: 070 * 1) a set of classes will be loaded in order to execute their static init block 071 * 2) retrieve and set the current Smack release 072 * 3) set DEBUG 073 */ 074 static { 075 String smackVersion; 076 BufferedReader reader = null; 077 try { 078 reader = new BufferedReader(new InputStreamReader(FileUtils.getStreamForClasspathFile("org.jivesoftware.smack/version", null), StandardCharsets.UTF_8)); 079 smackVersion = reader.readLine(); 080 } catch (Exception e) { 081 LOGGER.log(Level.SEVERE, "Could not determine Smack version", e); 082 smackVersion = "unknown"; 083 } finally { 084 CloseableUtil.maybeClose(reader, LOGGER); 085 } 086 SMACK_VERSION = smackVersion; 087 088 String disabledClasses = System.getProperty("smack.disabledClasses"); 089 if (disabledClasses != null) { 090 String[] splitDisabledClasses = disabledClasses.split(","); 091 for (String s : splitDisabledClasses) SmackConfiguration.disabledSmackClasses.add(s); 092 } 093 094 InputStream configFileStream; 095 try { 096 configFileStream = FileUtils.getStreamForClasspathFile(DEFAULT_CONFIG_FILE, null); 097 } 098 catch (Exception e) { 099 throw new IllegalStateException("Could not load Smack configuration file", e); 100 } 101 102 try { 103 processConfigFile(configFileStream, null); 104 } 105 catch (Exception e) { 106 throw new IllegalStateException("Could not parse Smack configuration file", e); 107 } 108 109 // Add the Java7 compression handler first, since it's preferred 110 SmackConfiguration.addCompressionHandler(new Java7ZlibInputOutputStream()); 111 112 XmppCompressionManager.registerXmppCompressionFactory(ZlibXmppCompressionFactory.INSTANCE); 113 114 // Use try block since we may not have permission to get a system 115 // property (for example, when an applet). 116 try { 117 // Only overwrite DEBUG if it is set via the 'smack.debugEnabled' property. To prevent DEBUG_ENABLED 118 // = true, which could be set e.g. via a static block from user code, from being overwritten by the property not set 119 if (Boolean.getBoolean("smack.debugEnabled")) { 120 SmackConfiguration.DEBUG = true; 121 } 122 } 123 catch (Exception e) { 124 LOGGER.log(Level.FINE, "Could not handle debugEnable property on Smack initialization", e); 125 } 126 127 SASLAuthentication.registerSASLMechanism(new SCRAMSHA1Mechanism()); 128 SASLAuthentication.registerSASLMechanism(new ScramSha1PlusMechanism()); 129 SASLAuthentication.registerSASLMechanism(new SASLXOauth2Mechanism()); 130 SASLAuthentication.registerSASLMechanism(new SASLAnonymous()); 131 132 ProviderManager.addIQProvider(Bind.ELEMENT, Bind.NAMESPACE, new BindIQProvider()); 133 ProviderManager.addExtensionProvider(Message.Body.ELEMENT, Message.Body.NAMESPACE, new BodyElementProvider()); 134 ProviderManager.addExtensionProvider(Message.Thread.ELEMENT, Message.Thread.NAMESPACE, new MessageThreadElementProvider()); 135 ProviderManager.addExtensionProvider(Message.Subject.ELEMENT, Message.Subject.NAMESPACE, new MessageSubjectElementProvider()); 136 137 ProviderManager.addNonzaProvider(SaslChallengeProvider.INSTANCE); 138 ProviderManager.addNonzaProvider(SaslSuccessProvider.INSTANCE); 139 ProviderManager.addNonzaProvider(SaslFailureProvider.INSTANCE); 140 ProviderManager.addNonzaProvider(TlsProceedProvider.INSTANCE); 141 ProviderManager.addNonzaProvider(TlsFailureProvider.INSTANCE); 142 ProviderManager.addNonzaProvider(CompressedProvider.INSTANCE); 143 ProviderManager.addNonzaProvider(FailureProvider.INSTANCE); 144 145 SmackConfiguration.addModule(Bind2ModuleDescriptor.class); 146 SmackConfiguration.addModule(CompressionModuleDescriptor.class); 147 SmackConfiguration.addModule(InstantStreamResumptionModuleDescriptor.class); 148 149 SmackConfiguration.smackInitialized = true; 150 } 151 152 public static void processConfigFile(InputStream cfgFileStream, 153 Collection<Exception> exceptions) throws Exception { 154 processConfigFile(cfgFileStream, exceptions, SmackInitialization.class.getClassLoader()); 155 } 156 157 public static void processConfigFile(InputStream cfgFileStream, 158 Collection<Exception> exceptions, ClassLoader classLoader) throws Exception { 159 XmlPullParser parser = PacketParserUtils.getParserFor(cfgFileStream); 160 XmlPullParser.Event eventType = parser.getEventType(); 161 do { 162 if (eventType == XmlPullParser.Event.START_ELEMENT) { 163 if (parser.getName().equals("startupClasses")) { 164 parseClassesToLoad(parser, false, exceptions, classLoader); 165 } 166 else if (parser.getName().equals("optionalStartupClasses")) { 167 parseClassesToLoad(parser, true, exceptions, classLoader); 168 } 169 } 170 eventType = parser.next(); 171 } 172 while (eventType != XmlPullParser.Event.END_DOCUMENT); 173 CloseableUtil.maybeClose(cfgFileStream, LOGGER); 174 } 175 176 private static void parseClassesToLoad(XmlPullParser parser, boolean optional, 177 Collection<Exception> exceptions, ClassLoader classLoader) 178 throws Exception { 179 final String startName = parser.getName(); 180 XmlPullParser.Event eventType; 181 outerloop: do { 182 eventType = parser.next(); 183 if (eventType == XmlPullParser.Event.START_ELEMENT && "className".equals(parser.getName())) { 184 String classToLoad = parser.nextText(); 185 if (SmackConfiguration.isDisabledSmackClass(classToLoad)) { 186 continue outerloop; 187 } 188 189 try { 190 loadSmackClass(classToLoad, optional, classLoader); 191 } catch (Exception e) { 192 // Don't throw the exception if an exceptions collection is given, instead 193 // record it there. This is used for unit testing purposes. 194 if (exceptions != null) { 195 exceptions.add(e); 196 } else { 197 throw e; 198 } 199 } 200 } 201 } 202 while (!(eventType == XmlPullParser.Event.END_ELEMENT && startName.equals(parser.getName()))); 203 } 204 205 private static void loadSmackClass(String className, boolean optional, ClassLoader classLoader) throws Exception { 206 Class<?> initClass; 207 try { 208 // Attempt to load and initialize the class so that all static initializer blocks of 209 // class are executed 210 initClass = Class.forName(className, true, classLoader); 211 } 212 catch (ClassNotFoundException cnfe) { 213 Level logLevel; 214 if (optional) { 215 logLevel = Level.FINE; 216 } 217 else { 218 logLevel = Level.WARNING; 219 } 220 LOGGER.log(logLevel, "A startup class '" + className + "' could not be loaded."); 221 if (!optional) { 222 throw cnfe; 223 } else { 224 return; 225 } 226 } 227 if (SmackInitializer.class.isAssignableFrom(initClass)) { 228 SmackInitializer initializer = (SmackInitializer) initClass.getConstructor().newInstance(); 229 List<Exception> exceptions = initializer.initialize(); 230 if (exceptions == null || exceptions.size() == 0) { 231 LOGGER.log(Level.FINE, "Loaded SmackInitializer " + className); 232 } else { 233 for (Exception e : exceptions) { 234 LOGGER.log(Level.SEVERE, "Exception in loadSmackClass", e); 235 } 236 } 237 } else { 238 LOGGER.log(Level.FINE, "Loaded " + className); 239 } 240 } 241}