//----------------------------BEGIN LICENSE----------------------------
/* 
 * Willow : the Open Source WorkFlow Project
 * Distributable under GNU LGPL license by gun.org
 *
 * Copyright (C) 2004-2010 huihoo.org 
 * Copyright (C) 2004-2010  ZosaTapo <dertyang@hotmail.com> 
 * 
 * ====================================================================
 * Project Homepage : http://www.huihoo.org/willow
 * Source Forge     : http://sourceforge.net/projects/huihoo
 * Mailing list     : willow@lists.sourceforge.net 
 */
//----------------------------END  LICENSE----------------------------

package org.huihoo.willow.realm;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.huihoo.willow.Container;
import org.huihoo.willow.Lifecycle;
import org.huihoo.willow.LifecycleException;
import org.huihoo.willow.LifecycleListener;
import org.huihoo.willow.Logger;
import org.huihoo.willow.RealmDatabase;
import org.huihoo.willow.util.HexUtils;
import org.huihoo.willow.util.LifecycleSupport;
import org.huihoo.willow.util.MD5Encoder;
import org.huihoo.willow.util.StringManager;
import org.huihoo.workflow.usermodel.WorkflowParticipant;

public abstract class RealmBase implements Lifecycle, RealmDatabase
{

	protected static Log log = LogFactory.getLog(RealmBase.class);

	/**
	 * The string manager for this package.
	 */
	protected static StringManager sm = StringManager.getManager(Constants.PACKAGE);
	
	// ----------------------------------------------------- Instance Variables

	/**
	 * The Container with which this Realm is associated.
	 */
	protected Container container = null;

	/**
	 * The debugging detail level for this component.
	 */
	protected int debug = 0;

	/**
	 * Digest algorithm used in storing passwords in a non-plaintext format.
	 * Valid values are those accepted for the algorithm name by the
	 * MessageDigest class, or <code>null</code> if no digesting should
	 * be performed.
	 */
	protected String digest = null;

	/**
	 * Descriptive information about this RealmDatabase implementation.
	 */
	protected static final String info = "org.huihoo.willow.realm.RealmBase/1.0";

	/**
	 * The lifecycle event support for this component.
	 */
	protected LifecycleSupport lifecycle = new LifecycleSupport(this);

	/**
	 * The MessageDigest object for digesting user credentials (passwords).
	 */
	protected MessageDigest md = null;

	/**
	 * The MD5 helper object for this class.
	 */
	protected static final MD5Encoder md5Encoder = new MD5Encoder();

	/**
	 * MD5 message digest provider.
	 */
	protected static MessageDigest md5Helper;

	/**
	 * Has this component been started?
	 */
	protected boolean started = false;

	/**
	 * The property change support for this component.
	 */
	protected PropertyChangeSupport support = new PropertyChangeSupport(this);

	// ------------------------------------------------------------- Properties

	/**
	 * Return the Container with which this Realm has been associated.
	 */
	public Container getContainer()
	{

		return (container);

	}

	/**
	 * Set the Container with which this Realm has been associated.
	 *
	 * @param container The associated Container
	 */
	public void setContainer(Container container)
	{

		Container oldContainer = this.container;
		this.container = container;
		support.firePropertyChange("container", oldContainer, this.container);

	}

	/**
	 * Return the debugging detail level for this component.
	 */
	public int getDebug()
	{

		return (this.debug);

	}

	/**
	 * Set the debugging detail level for this component.
	 *
	 * @param debug The new debugging detail level
	 */
	public void setDebug(int debug)
	{

		this.debug = debug;

	}

	/**
	 * Return the digest algorithm  used for storing credentials.
	 */
	public String getDigest()
	{

		return digest;

	}

	/**
	 * Set the digest algorithm used for storing credentials.
	 *
	 * @param digest The new digest algorithm
	 */
	public void setDigest(String digest)
	{

		this.digest = digest;

	}

	/**
	 * Return descriptive information about this Realm implementation and
	 * the corresponding version number, in the format
	 * <code>&lt;description&gt;/&lt;version&gt;</code>.
	 */
	public String getInfo()
	{

		return info;

	}

	// --------------------------------------------------------- Public Methods

	/**
	 * Add a property change listener to this component.
	 *
	 * @param listener The listener to add
	 */
	public void addPropertyChangeListener(PropertyChangeListener listener)
	{

		support.addPropertyChangeListener(listener);

	}

	/**
	 * Remove a property change listener from this component.
	 *
	 * @param listener The listener to remove
	 */
	public void removePropertyChangeListener(PropertyChangeListener listener)
	{

		support.removePropertyChangeListener(listener);

	}

	// ------------------------------------------------------ Lifecycle Methods

	/**
	 * Add a lifecycle event listener to this component.
	 *
	 * @param listener The listener to add
	 */
	public void addLifecycleListener(LifecycleListener listener)
	{

		lifecycle.addLifecycleListener(listener);

	}

	/**
	 * Get the lifecycle listeners associated with this lifecycle. If this 
	 * Lifecycle has no listeners registered, a zero-length array is returned.
	 */
	public LifecycleListener[] findLifecycleListeners()
	{

		return lifecycle.findLifecycleListeners();

	}

	/**
	 * Remove a lifecycle event listener from this component.
	 *
	 * @param listener The listener to remove
	 */
	public void removeLifecycleListener(LifecycleListener listener)
	{

		lifecycle.removeLifecycleListener(listener);

	}

	/**
	 * Prepare for the beginning of active use of the public methods of this
	 * component.  This method should be called before any of the public
	 * methods of this component are utilized.  It should also send a
	 * LifecycleEvent of type START_EVENT to any registered listeners.
	 *
	 * @exception LifecycleException if this component detects a fatal error
	 *  that prevents this component from being used
	 */
	public void start() throws LifecycleException
	{

		// Validate and update our current component state
		if (started)
		{
			log.info(sm.getString("realmBase.alreadyStarted"));
			return;
		}

		lifecycle.fireLifecycleEvent(START_EVENT, null);
		started = true;

		// Create a MessageDigest instance for credentials, if desired
		if (digest != null)
		{
			try
			{
				md = MessageDigest.getInstance(digest);
			}
			catch (NoSuchAlgorithmException e)
			{
				throw new LifecycleException(sm.getString("realmBase.algorithm", digest), e);
			}
		}

	}

	/**
	 * Gracefully terminate the active use of the public methods of this
	 * component.  This method should be the last one called on a given
	 * instance of this component.  It should also send a LifecycleEvent
	 * of type STOP_EVENT to any registered listeners.
	 *
	 * @exception LifecycleException if this component detects a fatal error
	 *  that needs to be reported
	 */
	public void stop() throws LifecycleException
	{

		// Validate and update our current component state
		if (!started)
		{
			log.info(sm.getString("realmBase.notStarted"));
			return;
		}
		lifecycle.fireLifecycleEvent(STOP_EVENT, null);
		started = false;

		// Clean up allocated resources
		md = null;
	}

	// ------------------------------------------------------ Protected Methods

	/**
	 * Digest the password using the specified algorithm and
	 * convert the result to a corresponding hexadecimal string.
	 * If exception, the plain credentials string is returned.
	 *
	 * <strong>IMPLEMENTATION NOTE</strong> - This implementation is
	 * synchronized because it reuses the MessageDigest instance.
	 * This should be faster than cloning the instance on every request.
	 *
	 * @param credentials Password or other credentials to use in
	 *  authenticating this username
	 */
	protected String digest(String credentials)
	{

		// If no MessageDigest instance is specified, return unchanged
		if (hasMessageDigest() == false)
			return (credentials);

		// Digest the user credentials and return as hexadecimal
		synchronized (this)
		{
			try
			{
				md.reset();
				md.update(credentials.getBytes());
				return (HexUtils.convert(md.digest()));
			}
			catch (Exception e)
			{
				log.error(sm.getString("realmBase.digest"), e);
				return (credentials);
			}
		}

	}

	protected boolean hasMessageDigest()
	{
		return !(md == null);
	}

	/**
	 * Return the digest associated with given principal's user name.
	 */
	protected String getDigest(String username, String realmName)
	{
		if (md5Helper == null)
		{
			try
			{
				md5Helper = MessageDigest.getInstance("MD5");
			}
			catch (NoSuchAlgorithmException e)
			{
				e.printStackTrace();
				throw new IllegalStateException();
			}
		}
		String digestValue = username + ":" + realmName + ":" + getPassword(username);
		byte[] digest = md5Helper.digest(digestValue.getBytes());
		return md5Encoder.encode(digest);
	}

	/**
	 * Return a short name for this Realm implementation, for use in
	 * log messages.
	 */
	protected abstract String getName();

	/**
	 * Return the password associated with the given principal's user name.
	 */
	protected abstract String getPassword(String username);

	/**
	 * Return the Participant associated with the given user name.
	 */
	protected abstract WorkflowParticipant getParticipant(String username);

	/**
	 * Log a message on the Logger associated with our Container (if any)
	 *
	 * @param message Message to be logged
	 */
	protected void log(String message)
	{

		Logger logger = null;
		String name = null;
		if (container != null)
		{
			logger = container.getLogger();
			name = container.getName();
		}

		if (logger != null)
		{
			logger.log(getName() + "[" + name + "]: " + message);
		}
		else
		{
			System.out.println(getName() + "[" + name + "]: " + message);
		}

	}

	/**
	 * Log a message on the Logger associated with our Container (if any)
	 *
	 * @param message Message to be logged
	 * @param throwable Associated exception
	 */
	protected void log(String message, Throwable throwable)
	{

		Logger logger = null;
		String name = null;
		if (container != null)
		{
			logger = container.getLogger();
			name = container.getName();
		}

		if (logger != null)
		{
			logger.log(getName() + "[" + name + "]: " + message, throwable);
		}
		else
		{
			System.out.println(getName() + "[" + name + "]: " + message);
			throwable.printStackTrace(System.out);
		}
	}

	// --------------------------------------------------------- Static Methods

	/**
	 * Digest password using the algorithm especificied and
	 * convert the result to a corresponding hex string.
	 * If exception, the plain credentials string is returned
	 *
	 * @param credentials Password or other credentials to use in
	 *  authenticating this username
	 * @param algorithm Algorithm used to do th digest
	 */
	public final static String Digest(String credentials, String algorithm)
	{

		try
		{
			// Obtain a new message digest with "digest" encryption
			MessageDigest md = (MessageDigest) MessageDigest.getInstance(algorithm).clone();
			// encode the credentials
			md.update(credentials.getBytes());
			// Digest the credentials and return as hexadecimal
			return (HexUtils.convert(md.digest()));
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
			return credentials;
		}

	}

	/**
	 * Digest password using the algorithm especificied and
	 * convert the result to a corresponding hex string.
	 * If exception, the plain credentials string is returned
	 */
	public static void main(String args[])
	{

		if (args.length > 2 && args[0].equalsIgnoreCase("-a"))
		{
			for (int i = 2; i < args.length; i++)
			{
				System.out.print(args[i] + ":");
				System.out.println(Digest(args[i], args[1]));
			}
		}
		else
		{
			System.out.println("Usage: RealmBase -a <algorithm> <credentials>");
		}

	}

}