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