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}