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.XmlPullParserException;
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 = "classpath: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.getStreamForUrl("classpath: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 = "unkown";
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        try {
084            FileUtils.addLines("classpath:org.jivesoftware.smack/disabledClasses", SmackConfiguration.disabledSmackClasses);
085        }
086        catch (Exception e) {
087            throw new IllegalStateException(e);
088        }
089
090        try {
091            Class<?> c = Class.forName("org.jivesoftware.smack.CustomSmackConfiguration");
092            Field f = c.getField("DISABLED_SMACK_CLASSES");
093            String[] sa = (String[]) f.get(null);
094            if (sa != null) {
095                LOGGER.warning("Using CustomSmackConfig is deprecated and will be removed in a future release");
096                for (String s : sa)
097                    SmackConfiguration.disabledSmackClasses.add(s);
098            }
099        }
100        catch (ClassNotFoundException e1) {
101        }
102        catch (NoSuchFieldException e) {
103        }
104        catch (SecurityException e) {
105        }
106        catch (IllegalArgumentException e) {
107        }
108        catch (IllegalAccessException e) {
109        }
110
111        InputStream configFileStream;
112        try {
113            configFileStream = FileUtils.getStreamForUrl(DEFAULT_CONFIG_FILE, null);
114        }
115        catch (Exception e) {
116            throw new IllegalStateException(e);
117        }
118
119        try {
120            processConfigFile(configFileStream, null);
121        }
122        catch (Exception e) {
123            throw new IllegalStateException(e);
124        }
125
126        // Add the Java7 compression handler first, since it's preferred
127        SmackConfiguration.compressionHandlers.add(new Java7ZlibInputOutputStream());
128
129        // Use try block since we may not have permission to get a system
130        // property (for example, when an applet).
131        try {
132            // Only overwrite DEBUG if it is set via the 'smack.debugEnabled' property. To prevent DEBUG_ENABLED
133            // = true, which could be set e.g. via a static block from user code, from being overwritten by the property not set
134            if (Boolean.getBoolean("smack.debugEnabled")) {
135                SmackConfiguration.DEBUG = true;
136            }
137        }
138        catch (Exception e) {
139            // Ignore.
140        }
141
142        SASLAuthentication.registerSASLMechanism(new SCRAMSHA1Mechanism());
143        SASLAuthentication.registerSASLMechanism(new ScramSha1PlusMechanism());
144        SASLAuthentication.registerSASLMechanism(new SASLXOauth2Mechanism());
145        SASLAuthentication.registerSASLMechanism(new SASLAnonymous());
146
147        ProviderManager.addIQProvider(Bind.ELEMENT, Bind.NAMESPACE, new BindIQProvider());
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 = XmlPullParserFactory.newInstance().newPullParser();
160        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
161        parser.setInput(cfgFileStream, "UTF-8");
162        int eventType = parser.getEventType();
163        do {
164            if (eventType == XmlPullParser.START_TAG) {
165                if (parser.getName().equals("startupClasses")) {
166                    parseClassesToLoad(parser, false, exceptions, classLoader);
167                }
168                else if (parser.getName().equals("optionalStartupClasses")) {
169                    parseClassesToLoad(parser, true, exceptions, classLoader);
170                }
171            }
172            eventType = parser.next();
173        }
174        while (eventType != XmlPullParser.END_DOCUMENT);
175        try {
176            cfgFileStream.close();
177        }
178        catch (IOException e) {
179            LOGGER.log(Level.SEVERE, "Error while closing config file input stream", e);
180        }
181    }
182
183    private static void parseClassesToLoad(XmlPullParser parser, boolean optional,
184                    Collection<Exception> exceptions, ClassLoader classLoader)
185                    throws XmlPullParserException, IOException, Exception {
186        final String startName = parser.getName();
187        int eventType;
188        String name;
189        outerloop: do {
190            eventType = parser.next();
191            name = parser.getName();
192            if (eventType == XmlPullParser.START_TAG && "className".equals(name)) {
193                String classToLoad = parser.nextText();
194                if (SmackConfiguration.isDisabledSmackClass(classToLoad)) {
195                    continue outerloop;
196                }
197
198                try {
199                    loadSmackClass(classToLoad, optional, classLoader);
200                } catch (Exception e) {
201                    // Don't throw the exception if an exceptions collection is given, instead
202                    // record it there. This is used for unit testing purposes.
203                    if (exceptions != null) {
204                        exceptions.add(e);
205                    } else {
206                        throw e;
207                    }
208                }
209            }
210        }
211        while (!(eventType == XmlPullParser.END_TAG && startName.equals(name)));
212    }
213
214    private static void loadSmackClass(String className, boolean optional, ClassLoader classLoader) throws Exception {
215        Class<?> initClass;
216        try {
217            // Attempt to load and initialize the class so that all static initializer blocks of
218            // class are executed
219            initClass = Class.forName(className, true, classLoader);
220        }
221        catch (ClassNotFoundException cnfe) {
222            Level logLevel;
223            if (optional) {
224                logLevel = Level.FINE;
225            }
226            else {
227                logLevel = Level.WARNING;
228            }
229            LOGGER.log(logLevel, "A startup class '" + className + "' could not be loaded.");
230            if (!optional) {
231                throw cnfe;
232            } else {
233                return;
234            }
235        }
236        if (SmackInitializer.class.isAssignableFrom(initClass)) {
237            SmackInitializer initializer = (SmackInitializer) initClass.getConstructor().newInstance();
238            List<Exception> exceptions = initializer.initialize();
239            if (exceptions == null || exceptions.size() == 0) {
240                LOGGER.log(Level.FINE, "Loaded SmackInitializer " + className);
241            } else {
242                for (Exception e : exceptions) {
243                    LOGGER.log(Level.SEVERE, "Exception in loadSmackClass", e);
244                }
245            }
246        } else {
247            LOGGER.log(Level.FINE, "Loaded " + className);
248        }
249    }
250}