/* Copyright (c) 2001,2002 Oracle Corporation. All Rights Reserved */
package oracle.portal.log;

import java.util.Map;
import java.util.Set;
import java.util.HashMap;
import java.util.Iterator;

import oracle.webdb.log.NullLogger;

/**
* LogManager is a configurable logging service that allows you to log 
* messages at 7 different levels ranging from configuration to debug. 
*
* To configure LogManager, you must provide an implementation of the {@link
* Logger} class.  It is the Logger that performs the actual logging.
*
* The LogManager is initialized by calling the {@link #init(Logger)} method and
* destroyed by calling the {@link #destroyAll()} or {@link #destroy(String)}
* method. 
*
* It is important that calls to LogManager can be made AT ANY TIME regardless 
* of the initialization state of the LogManager. For this reason, When a 
* LogManager is created, it uses a NullLogger that "swallows" any messages
* logged to it.
*
* IMPORTANT: The semantics of LogManager do not allow any exceptions to be
*            thrown. Any failures should be logged (if possible) and swallowed.
*            A configuration issue in LogManager or a Logger should not 
*            interfere with the operation of any clients
*/
public final class LogManager
{
    /**
     * calls to config() and log() result in messages being logged
     */ 
    public static int LOG_LEVEL_CONFIG = 1;

    /**
     * calls to severe(), config() and log() result in messages being logged
     */ 
    public static int LOG_LEVEL_ERROR = 2;

    /**
     * calls to warning(), severe(), config() and log() result in messages 
     * being logged
     */ 
    public static int LOG_LEVEL_WARNING = 3;
    
    /**
     * calls to throwing(), warning(), severe(), config() and log() result 
     * in messages being logged
     */ 
    public static int LOG_LEVEL_EXCEPTION = 4;
    
    /**
     * calls to perf(), throwing(), warning(), severe(), config() 
     * and log() result in messages being logged
     */ 
    public static int LOG_LEVEL_PERF = 5;
    
    /**
     * calls to Info(), perf(), throwing(), warning(), severe(), config() and log() 
     * result in messages being logged
     */ 
    public static int LOG_LEVEL_INFO = 6;

    /**
     * all logging calls result in messages being logged
     */ 
    public static int LOG_LEVEL_DEBUG = 7; 
    

    /**
     * NullLogger that is shared by everyone requiring a NullLogger reference.
     *
     * NullLogger is returned when a context has not been initialized or a 
     * thread has not been initialized by calling 
     * {@link #initCurrentThread(String, MessageContext)}
     */
    private static Logger sNullLogger = new NullLogger();

    /**
     * LogManager instance that is returned by calls to 
     * {@link #getInstance()}. This is really only useful for pinning the 
     * singleton since the instance does not have any callable methods.
     */
    private static LogManager sLogManager = new LogManager();

    /**
     * object used to synchronize initialization and access to sLoggers
     */
    private static Object sLock = new Object();

    /**
    * HashMap for storing mapping of Context Path -> initialized Loggers.
    * 
    * A HashMap is used instead of an ArrayList so it is possible to remove
    * contexts from the collection after they are destroyed. Using an ArrayList
    * not allow this since removal of an entry would change the index (and 
    * therefore the context identifier of all the loggers following that which
    * was removed. 
    */
    private static HashMap sLoggers = new HashMap(20);

    /**
    * ThreadLocal variable initialized on a per-request basis to the Logger
    * associated with the context specified in the call to
    * {@link #initCurrentThread(String, MessageContext)}.
    */
    private static ThreadLocal sThreadLogger = 
        new ThreadLocal()
        {
            protected Object initialValue()
            {
                return sNullLogger;
            }
        };

    /**
    * Counter used to assign the next context id. A counter is used so the
    * same ID is not assigned to 2 contexts, even when old contexts are 
    * destroyed
    */
    private static int sNextContextId = 0;
    

    /**
     * Perform one-time initialization for a new LogManager context.
     * 
     * This method lets you initialize a new logging context. The context
     * identifier is assigned by the LogManager to prevent collisions with
     * other logging contexts that may be in use within other applications
     * 
     * @param logger the pre-initialized Logger that should be associated with 
     *        the new context
     * @return the context identifier 
     */ 
    public static String init(Logger logger)
    {
        String contextKey;
        synchronized(sLock)
        {
            contextKey = String.valueOf(sNextContextId++);
            sLoggers.put(contextKey, logger);
        }
        return contextKey;
    }


    /**
     * Do per-thread initialization of the LogManager.
     * 
     * Arguments needed to specify and configure a Logger should be specified
     * 
     * @param context identifier of the logging context to be associated with 
     *        the calling thread
     * @param msgContext MessageContext to be used by the Logger when logging
     *        messages from the calling thread
     */ 
    public static void initCurrentThread(String context, 
                                         MessageContext msgContext)
    {
        Logger logger = getLogger(context);
        sThreadLogger.set(logger);
        logger.initCurrentThread(msgContext);
    }
    
    
    /**
     * Do per-thread clean-up to prevent accidental carry-over of context 
     * values when this thread is re-used (eg for another request).
     */ 
    public static void resetCurrentThread()
    {
        Logger logger = getLogger();
        sThreadLogger.set(sNullLogger);
        logger.resetCurrentThread();
    }

    
    /**
     * destroy all configured loggers.
     */    
    public static void destroyAll()
    {
        synchronized(sLock)
        {
            Set keys = sLoggers.keySet();
            Iterator iter = keys.iterator();
            while(iter.hasNext())
            {
                destroy((String)iter.next());
            }
        }
    }
    
    
    /**
     * destroy a specified configured Logger
     *
     * @param context the context name of the Logger to be destroyed
     */    
    public static void destroy(String context)
    {
        // sync this so you cannot init & destroy a Logger at the same
        // time.
        synchronized(sLock)
        {
            Logger logger = getLogger(context);
            
            // if the logger being destroyed is this thread's logger, update
            // the ThreadLocal variable to point to the NullLogger. If this
            // thread is destroying a different context, don't change the 
            // ThreadLocal because you will prevent the current thread from
            // logging future messages.
            if (logger == getLogger())
            {
                sThreadLogger.set(sNullLogger);
            }
            sLoggers.remove(context);
            logger.destroy();
        }
    }


    /**
     * returns a LogManager instance.
     *
     * This method is only useful for pinning the LogManager singleton because
     * there are no publically accessible methods on the instance
     *
     * @return LogManager instance 
     */
    public static LogManager getInstance()
    {
        return sLogManager;
    }


    /**
     * returns the logger associated with the current thread.
     *
     * if {@link #initCurrentThread(String, MessageContext)} has not been called 
     * for the current thread, the NullLogger will be returned
     * 
     * @return Logger associated with the current thread.
     */
    public static Logger getLogger()
    {
        // sThreadLogger is initialized to return sNullLogger & 
        // resetCurrentThread sets the logger to sNullLogger so we are
        // guaranteed to get a Logger object
        return (Logger)sThreadLogger.get();
    }
    
    
    /**
     * returns the logger associated with the specified context.
     *
     * if {@link #initCurrentThread(String, MessageContext)} has not been 
     * called for the current thread, the NullLogger will be returned
     * 
     * @param context context identifier for the requested Logger
     * @return Logger associated with the current thread.
     */
    public static Logger getLogger(String context)
    {
        Logger logger;
        synchronized(sLock)
        {
            logger = (Logger)sLoggers.get(context);
        }

        // if this context has not been initialized, return the NullLogger
        if (logger == null)
        {
            logger =  sNullLogger;
        }
        return logger;
    }
    
    
    /**
     * Logs the given message. The object implementing {@link Logger} used by
     * this <code>LogManager</code> is declared and configured in the
     * LogManager XML Definition.
     *
     * @param message message to log.
     */
    public static void log(String message)
    {
        getLogger().log(message);
    }

    /**
     * Logs the given <code>java.lang.Throwable</code> object, e.g. by logging
     * the stack trace, along with the given message. The object implementing
     * {@link Logger} used by this <code>LogManager</code> is declared and
     * configured in the LogManager XML Definition.
     *
     * @param message explanatory message.
     * @param throwable a <code>Throwable</code> exception.
     */
    public static void log(String message, Throwable throwable)
    {
        getLogger().log(message, throwable);
    }

    
    /**
     * Logs the given message. The object implementing {@link Logger} used by
     * this <code>LogManager</code> is declared and configured in the
     * LogManager XML Definition.
     *
     * @param message message to log.
     */
    public static void config(String message)
    {
        getLogger().config(message);
    }

    /**
     * Logs the given <code>java.lang.Throwable</code> object, e.g. by logging
     * the stack trace, along with the given message. The object implementing
     * {@link Logger} used by this <code>LogManager</code> is declared and
     * configured in the LogManager XML Definition.
     *
     * @param message explanatory message.
     * @param throwable a <code>Throwable</code> exception.
     */
    public static void config(String message, Throwable throwable)
    {
        getLogger().config(message, throwable);
    }


    /**
     * Logs the given message. The object implementing {@link Logger} used by
     * this <code>LogManager</code> is declared and configured in the
     * LogManager XML Definition.
     *
     * @param message message to log.
     */
    public static void severe(String message)
    {
        getLogger().severe(message);
    }

    /**
     * Logs the given <code>java.lang.Throwable</code> object, e.g. by logging
     * the stack trace, along with the given message. The object implementing
     * {@link Logger} used by this <code>LogManager</code> is declared and
     * configured in the LogManager XML Definition.
     *
     * @param message explanatory message.
     * @param throwable a <code>Throwable</code> exception.
     */
    public static void severe(String message, Throwable throwable)
    {
        getLogger().severe(message, throwable);
    }


    /**
     * Logs the given message. The object implementing {@link Logger} used by
     * this <code>LogManager</code> is declared and configured in the
     * LogManager XML Definition.
     *
     * @param message message to log.
     */
    public static void warning(String message)
    {
        getLogger().warning(message);
    }


    /**
     * Logs the given <code>java.lang.Throwable</code> object, e.g. by logging
     * the stack trace, along with the given message. The object implementing
     * {@link Logger} used by this <code>LogManager</code> is declared and
     * configured in the LogManager XML Definition.
     *
     * @param message explanatory message.
     * @param throwable a <code>Throwable</code> exception.
     */
    public static void warning(String message, Throwable throwable)
    {
        getLogger().warning(message, throwable);
    }


    /**
     *
     * @param message explanatory message.
     * @param throwable a <code>Throwable</code> exception.
     */
    public static void throwing(String message, Throwable throwable)
    {
        getLogger().throwing(message, throwable);
    }


    /**
     * Logs the given message. The object implementing {@link Logger} used by
     * this <code>LogManager</code> is declared and configured in the
     * LogManager XML Definition.
     *
     * @param message message to log.
     */
    public static void info(String message)
    {
        getLogger().info(message);
    }


    /**
     * Logs the given message. The object implementing {@link Logger} used by
     * this <code>LogManager</code> is declared and configured in the
     * LogManager XML Definition.
     *
     * @param message message to log.
     */
    public static void perf(String message)
    {
        getLogger().perf(message);
    }


    /**
     * Logs the given message. The object implementing {@link Logger} used by
     * this <code>LogManager</code> is declared and configured in the
     * LogManager XML Definition.
     *
     * @param message message to log.
     */
    public static void debug(String message)
    {
        getLogger().debug(message);
    }

    /**
     * Returns true if Configuration level logging is enabled. Otherwise false.
     *
     * @return true if configuration level logging is enabled. Otherwise false.
     */
    public static boolean isConfigEnabled()
    {
        return getLogger().isConfigEnabled();
    }

    /**
     * Returns true if Severe Error level logging is enabled. Otherwise false.
     *
     * @return true if severe error level logging is enabled. Otherwise false.
     */
    public static boolean isSevereEnabled()
    {
        return getLogger().isSevereEnabled();
    }

    /**
     * Returns true if Throwing (exception) level logging is enabled, otherwise 
     * false.
     *
     * @return true if Throwing level logging is enabled. Otherwise false.
     */
    public static boolean isThrowingEnabled()
    {
        return getLogger().isThrowingEnabled();
    }

    /**
     * Returns true if Warning level logging is enabled. Otherwise false.
     *
     * @return true if warning level logging is enabled. Otherwise false.
     */
    public static boolean isWarningEnabled()
    {
        return getLogger().isWarningEnabled();
    }

    /**
     * Returns true if Information level logging is enabled. Otherwise false.
     *
     * @return true if Information level logging is enabled. Otherwise false.
     */
    public static boolean isInfoEnabled()
    {
        return getLogger().isInfoEnabled();
    }

    /**
     * Returns true if Performance level logging is enabled. Otherwise false.
     *
     * @return true if performance level logging is enabled. Otherwise false.
     */
    public static boolean isPerfEnabled()
    {
        return getLogger().isPerfEnabled();
    }

    /**
     * Returns true if Debug level logging is enabled. Otherwise false.
     *
     * @return true if debug level logging is enabled. Otherwise false.
     */
    public static boolean isDebugEnabled()
    {
        return getLogger().isDebugEnabled();
    }


    /**
     * Private constructor
     *
     * Constructor is provided for the sole purpose of allowing the LogManager
     * class to instantiate an instance of itself so that an object can be
     * returned by the getInstance() method.
     */
    private LogManager()
    {
        // do nothing
    }

}
