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