001/**
002 *
003 * Copyright 2014 Vyacheslav Blinov, 2017 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 */
017package org.jivesoftware.smack.debugger;
018
019import java.lang.reflect.Constructor;
020import java.util.logging.Level;
021import java.util.logging.Logger;
022
023import org.jivesoftware.smack.SmackConfiguration;
024import org.jivesoftware.smack.XMPPConnection;
025
026public final class ReflectionDebuggerFactory implements SmackDebuggerFactory {
027    private static final Logger LOGGER = Logger.getLogger(ReflectionDebuggerFactory.class.getName());
028    private static final String DEBUGGER_CLASS_PROPERTY_NAME = "smack.debuggerClass";
029
030    public static final ReflectionDebuggerFactory INSTANCE = new ReflectionDebuggerFactory();
031
032    private ReflectionDebuggerFactory() {
033    }
034
035    /**
036     * Possible default debugger implementations. The order of enumeration is the one in which we try
037     * to instantiate these.
038     */
039    private static final String[] DEFAULT_DEBUGGERS = new String[] {
040            "org.jivesoftware.smackx.debugger.EnhancedDebugger",
041            "org.jivesoftware.smackx.debugger.android.AndroidDebugger",
042            "org.jivesoftware.smack.debugger.ConsoleDebugger",
043            "org.jivesoftware.smack.debugger.LiteDebugger",
044            "org.jivesoftware.smack.debugger.JulDebugger",
045            };
046
047    /**
048     * Sets custom debugger class to be created by this factory.
049     * @param debuggerClass class to be used by this factory
050     */
051    public static void setDebuggerClass(Class<? extends SmackDebugger> debuggerClass) {
052        if (debuggerClass == null) {
053            System.clearProperty(DEBUGGER_CLASS_PROPERTY_NAME);
054        } else {
055            System.setProperty(DEBUGGER_CLASS_PROPERTY_NAME, debuggerClass.getCanonicalName());
056        }
057    }
058
059    /**
060     * Returns debugger class used by this factory.
061     * @return debugger class that will be used for instantiation by this factory
062     */
063    @SuppressWarnings("unchecked")
064    public static Class<SmackDebugger> getDebuggerClass() {
065        String customDebuggerClassName = getCustomDebuggerClassName();
066        if (customDebuggerClassName == null) {
067            return getOneOfDefaultDebuggerClasses();
068        } else {
069            try {
070                return (Class<SmackDebugger>) Class.forName(customDebuggerClassName);
071            } catch (Exception e) {
072                LOGGER.log(Level.WARNING, "Unable to instantiate debugger class " + customDebuggerClassName, e);
073            }
074        }
075        // no suitable debugger class found - give up
076        return null;
077    }
078
079    @Override
080    public SmackDebugger create(XMPPConnection connection) throws IllegalArgumentException {
081        Class<SmackDebugger> debuggerClass = getDebuggerClass();
082        if (debuggerClass != null) {
083            // Create a new debugger instance using 3arg constructor
084            try {
085                Constructor<SmackDebugger> constructor = debuggerClass
086                        .getConstructor(XMPPConnection.class);
087                return constructor.newInstance(connection);
088            } catch (Exception e) {
089                throw new IllegalArgumentException("Can't initialize the configured debugger!", e);
090            }
091        }
092        return null;
093    }
094
095    private static String getCustomDebuggerClassName() {
096        try {
097            // Use try block since we may not have permission to get a system
098            // property (for example, when an applet).
099            return System.getProperty(DEBUGGER_CLASS_PROPERTY_NAME);
100        } catch (Throwable t) {
101            // Ignore.
102            return null;
103        }
104    }
105
106    @SuppressWarnings("unchecked")
107    private static Class<SmackDebugger> getOneOfDefaultDebuggerClasses() {
108        for (String debugger : DEFAULT_DEBUGGERS) {
109            if (SmackConfiguration.isDisabledSmackClass(debugger)) {
110                continue;
111            }
112            try {
113                return (Class<SmackDebugger>) Class.forName(debugger);
114            } catch (ClassNotFoundException cnfe) {
115                LOGGER.fine("Did not find debugger class '" + debugger + "'");
116            } catch (ClassCastException ex) {
117                LOGGER.warning("Found debugger class that does not appears to implement SmackDebugger interface");
118            } catch (Exception ex) {
119                LOGGER.warning("Unable to instantiate either Smack debugger class");
120            }
121        }
122        // did not found any of default debuggers - give up
123        return null;
124    }
125}