001/**
002 *
003 * Copyright 2003-2007 Jive Software.
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.Reader;
021import java.io.Writer;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Set;
028
029import javax.net.ssl.HostnameVerifier;
030
031import org.jivesoftware.smack.compression.XMPPInputOutputStream;
032import org.jivesoftware.smack.debugger.ReflectionDebuggerFactory;
033import org.jivesoftware.smack.debugger.SmackDebugger;
034import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
035import org.jivesoftware.smack.parsing.ExceptionThrowingCallback;
036import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
037import org.jivesoftware.smack.util.Objects;
038
039/**
040 * Represents the configuration of Smack. The configuration is used for:
041 * <ul>
042 *      <li> Initializing classes by loading them at start-up.
043 *      <li> Getting the current Smack version.
044 *      <li> Getting and setting global library behavior, such as the period of time
045 *          to wait for replies to packets from the server. Note: setting these values
046 *          via the API will override settings in the configuration file.
047 * </ul>
048 *
049 * Configuration settings are stored in org.jivesoftware.smack/smack-config.xml.
050 * 
051 * @author Gaston Dombiak
052 */
053public final class SmackConfiguration {
054
055    private static int defaultPacketReplyTimeout = 5000;
056    private static int packetCollectorSize = 5000;
057
058    private static List<String> defaultMechs = new ArrayList<String>();
059
060    static Set<String> disabledSmackClasses = new HashSet<String>();
061
062    final static List<XMPPInputOutputStream> compressionHandlers = new ArrayList<XMPPInputOutputStream>(2);
063
064    static boolean smackInitialized = false;
065
066    private static SmackDebuggerFactory debuggerFactory = new ReflectionDebuggerFactory();
067
068    /**
069     * Value that indicates whether debugging is enabled. When enabled, a debug
070     * window will apear for each new connection that will contain the following
071     * information:<ul>
072     * <li> Client Traffic -- raw XML traffic generated by Smack and sent to the server.
073     * <li> Server Traffic -- raw XML traffic sent by the server to the client.
074     * <li> Interpreted Packets -- shows XML packets from the server as parsed by Smack.
075     * </ul>
076     * <p/>
077     * Debugging can be enabled by setting this field to true, or by setting the Java system
078     * property <tt>smack.debugEnabled</tt> to true. The system property can be set on the
079     * command line such as "java SomeApp -Dsmack.debugEnabled=true".
080     */
081    public static boolean DEBUG = false;
082
083    /**
084     * The default parsing exception callback is {@link ExceptionThrowingCallback} which will
085     * throw an exception and therefore disconnect the active connection.
086     */
087    private static ParsingExceptionCallback defaultCallback = new ExceptionThrowingCallback();
088
089    private static HostnameVerifier defaultHostnameVerififer;
090
091    /**
092     * Returns the Smack version information, eg "1.3.0".
093     * 
094     * @return the Smack version information.
095     */
096    public static String getVersion() {
097        return SmackInitialization.SMACK_VERSION;
098    }
099
100    /**
101     * Returns the number of milliseconds to wait for a response from
102     * the server. The default value is 5000 ms.
103     * 
104     * @return the milliseconds to wait for a response from the server
105     * @deprecated use {@link #getDefaultReplyTimeout()} instead.
106     */
107    @Deprecated
108    public static int getDefaultPacketReplyTimeout() {
109        return getDefaultReplyTimeout();
110    }
111
112    /**
113     * Sets the number of milliseconds to wait for a response from
114     * the server.
115     * 
116     * @param timeout the milliseconds to wait for a response from the server
117     * @deprecated use {@link #setDefaultReplyTimeout(int)} instead.
118     */
119    @Deprecated
120    public static void setDefaultPacketReplyTimeout(int timeout) {
121        setDefaultReplyTimeout(timeout);
122    }
123
124    /**
125     * Returns the number of milliseconds to wait for a response from
126     * the server. The default value is 5000 ms.
127     * 
128     * @return the milliseconds to wait for a response from the server
129     */
130    public static int getDefaultReplyTimeout() {
131        // The timeout value must be greater than 0 otherwise we will answer the default value
132        if (defaultPacketReplyTimeout <= 0) {
133            defaultPacketReplyTimeout = 5000;
134        }
135        return defaultPacketReplyTimeout;
136    }
137
138    /**
139     * Sets the number of milliseconds to wait for a response from
140     * the server.
141     * 
142     * @param timeout the milliseconds to wait for a response from the server
143     */
144    public static void setDefaultReplyTimeout(int timeout) {
145        if (timeout <= 0) {
146            throw new IllegalArgumentException();
147        }
148        defaultPacketReplyTimeout = timeout;
149    }
150
151    /**
152     * Gets the default max size of a stanza(/packet) collector before it will delete 
153     * the older packets.
154     * 
155     * @return The number of packets to queue before deleting older packets.
156     */
157    public static int getStanzaCollectorSize() {
158        return packetCollectorSize;
159    }
160
161    /**
162     * Sets the default max size of a stanza(/packet) collector before it will delete 
163     * the older packets.
164     * 
165     * @param collectorSize the number of packets to queue before deleting older packets.
166     */
167    public static void setStanzaCollectorSize(int collectorSize) {
168        packetCollectorSize = collectorSize;
169    }
170
171    /**
172     * Add a SASL mechanism to the list to be used.
173     *
174     * @param mech the SASL mechanism to be added
175     */
176    public static void addSaslMech(String mech) {
177        if (!defaultMechs.contains(mech)) {
178            defaultMechs.add(mech);
179        }
180    }
181
182   /**
183     * Add a Collection of SASL mechanisms to the list to be used.
184     *
185     * @param mechs the Collection of SASL mechanisms to be added
186     */
187    public static void addSaslMechs(Collection<String> mechs) {
188        for (String mech : mechs) {
189            addSaslMech(mech);
190        }
191    }
192
193    /**
194     * Sets Smack debugger factory.
195     *
196     * @param debuggerFactory new debugger factory implementation to be used by Smack
197     */
198    public static void setDebuggerFactory(SmackDebuggerFactory debuggerFactory) {
199        SmackConfiguration.debuggerFactory = debuggerFactory;
200    }
201
202    /**
203     * Get the debugger factory.
204     *
205     * @return a debugger factory or <code>null</code>
206     */
207    public static SmackDebuggerFactory getDebuggerFactory() {
208        return debuggerFactory;
209    }
210
211    /**
212     * Creates new debugger instance with given arguments as parameters. May
213     * return <code>null</code> if no DebuggerFactory is set or if the factory
214     * did not produce a debugger.
215     * 
216     * @param connection
217     * @param writer
218     * @param reader
219     * @return a new debugger or <code>null</code>
220     */
221    public static SmackDebugger createDebugger(XMPPConnection connection, Writer writer, Reader reader) {
222        SmackDebuggerFactory factory = getDebuggerFactory();
223        if (factory == null) {
224            return null;
225        } else {
226            return factory.create(connection, writer, reader);
227        }
228    }
229
230    /**
231     * Remove a SASL mechanism from the list to be used.
232     *
233     * @param mech the SASL mechanism to be removed
234     */
235    public static void removeSaslMech(String mech) {
236        defaultMechs.remove(mech);
237    }
238
239   /**
240     * Remove a Collection of SASL mechanisms to the list to be used.
241     *
242     * @param mechs the Collection of SASL mechanisms to be removed
243     */
244    public static void removeSaslMechs(Collection<String> mechs) {
245        defaultMechs.removeAll(mechs);
246    }
247
248    /**
249     * Returns the list of SASL mechanisms to be used. If a SASL mechanism is
250     * listed here it does not guarantee it will be used. The server may not
251     * support it, or it may not be implemented.
252     *
253     * @return the list of SASL mechanisms to be used.
254     */
255    public static List<String> getSaslMechs() {
256        return Collections.unmodifiableList(defaultMechs);
257    }
258
259    /**
260     * Set the default parsing exception callback for all newly created connections.
261     *
262     * @param callback
263     * @see ParsingExceptionCallback
264     */
265    public static void setDefaultParsingExceptionCallback(ParsingExceptionCallback callback) {
266        defaultCallback = callback;
267    }
268
269    /**
270     * Returns the default parsing exception callback.
271     * 
272     * @return the default parsing exception callback
273     * @see ParsingExceptionCallback
274     */
275    public static ParsingExceptionCallback getDefaultParsingExceptionCallback() {
276        return defaultCallback;
277    }
278
279    public static void addCompressionHandler(XMPPInputOutputStream xmppInputOutputStream) {
280        compressionHandlers.add(xmppInputOutputStream);
281    }
282
283    public static List<XMPPInputOutputStream> getCompresionHandlers() {
284        List<XMPPInputOutputStream> res = new ArrayList<XMPPInputOutputStream>(compressionHandlers.size());
285        for (XMPPInputOutputStream ios : compressionHandlers) {
286            if (ios.isSupported()) {
287                res.add(ios);
288            }
289        }
290        return res;
291    }
292
293    /**
294     * Set the default HostnameVerifier that will be used by XMPP connections to verify the hostname
295     * of a TLS certificate. XMPP connections are able to overwrite this settings by supplying a
296     * HostnameVerifier in their ConnecitonConfiguration with
297     * {@link ConnectionConfiguration.Builder#setHostnameVerifier(HostnameVerifier)}.
298     */
299    public static void setDefaultHostnameVerifier(HostnameVerifier verifier) {
300        defaultHostnameVerififer = verifier;
301    }
302
303    /**
304     * Convenience method for {@link #addDisabledSmackClass(String)}.
305     *
306     * @param clz the Smack class to disable
307     */
308    public static void addDisabledSmackClass(Class<?> clz) {
309        addDisabledSmackClass(clz.getName());
310    }
311
312    /**
313     * Add a class to the disabled smack classes.
314     * <p>
315     * {@code className} can also be a package name, in this case, the entire
316     * package is disabled (but can be manually enabled).
317     * </p>
318     *
319     * @param className
320     */
321    public static void addDisabledSmackClass(String className) {
322        disabledSmackClasses.add(className);
323    }
324
325    /**
326     * Add the given class names to the list of disabled Smack classes.
327     *
328     * @param classNames the Smack classes to disable.
329     * @see #addDisabledSmackClass(String)
330     */
331    public static void addDisabledSmackClasses(String... classNames) {
332        for (String className : classNames) {
333            addDisabledSmackClass(className);
334        }
335    }
336
337    public static boolean isDisabledSmackClass(String className) {
338        for (String disabledClassOrPackage : disabledSmackClasses) {
339            if (disabledClassOrPackage.equals(className)) {
340                return true;
341            }
342            int lastDotIndex = disabledClassOrPackage.lastIndexOf('.');
343            // Security check to avoid NPEs if someone entered 'foo.bar.'
344            if (disabledClassOrPackage.length() > lastDotIndex
345                            // disabledClassOrPackage is not an Class
346                            && !Character.isUpperCase(disabledClassOrPackage.charAt(lastDotIndex + 1))
347                            // classToLoad startsWith the package disabledClassOrPackage disables
348                            && className.startsWith(disabledClassOrPackage)) {
349                // Skip the class because the whole package was disabled
350                return true;
351            }
352        }
353        return false;
354    }
355
356    /**
357     * Check if Smack was successfully initialized.
358     * 
359     * @return true if smack was initialized, false otherwise
360     */
361    public static boolean isSmackInitialized() {
362        return smackInitialized;
363    }
364
365    /**
366     * Get the default HostnameVerifier
367     *
368     * @return the default HostnameVerifier or <code>null</code> if none was set
369     */
370    static HostnameVerifier getDefaultHostnameVerifier() {
371        return defaultHostnameVerififer;
372    }
373
374    enum UnknownIqRequestReplyMode {
375        doNotReply,
376        replyFeatureNotImplemented,
377        replyServiceUnavailable,
378    }
379
380    // TODO Change to replyFeatureNotImplemented in Smack 4.3
381    private static UnknownIqRequestReplyMode unknownIqRequestReplyMode = UnknownIqRequestReplyMode.replyServiceUnavailable;
382
383    public static UnknownIqRequestReplyMode getUnknownIqRequestReplyMode() {
384        return unknownIqRequestReplyMode;
385    }
386
387    public static void setUnknownIqRequestReplyMode(UnknownIqRequestReplyMode unknownIqRequestReplyMode) {
388        SmackConfiguration.unknownIqRequestReplyMode = Objects.requireNonNull(unknownIqRequestReplyMode, "Must set mode");
389    }
390}