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