package gov.va.med.authentication.kernel;

import java.util.Set;

import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;

import org.apache.log4j.Logger;

/**
 * Obtains current values for the ListenPort, SSLListenPort and SSLListenPortEnabled values for the current server from
 * the ServerRuntime MBean. Compatible with WebLogic v9/10. Designed for thread safety.
 * 
 * Usage example: <code><pre>
 * 
 * try {
 * 	KaajeeSSLHelper helper = new KaajeeSSLHelper();
 *    System.out.println(helper.getListenPort());
 *    System.out.println(helper.isSSLListenPortEnabled());
 *    System.out.println(helper.getSSLListenPort();
 * } catch (Exception e) {
 * 	  System.err.println(e.getMessage();
 * }
 * </pre></code>
 * 
 */
public class KaajeeSSLHelper {

	private static Logger logger = Logger.getLogger(KaajeeSSLHelper.class);
	private static final String MBEANSERVER_JNDI_ENV_CONTEXT = "java:comp/env/jmx/runtime";
	private static final String MBEANSERVER_JNDI_CONTEXT = "java:comp/jmx/runtime";
	private static final String JMX_DOMAIN_FOR_PLATFORM = "com.bea";

	private int ListenPort = -1;
	private int SSLListenPort = -1;
	private boolean SSLListenPortEnabled = false;
	private boolean initialized = false;

	/**
	 * constructs instance, initializes values.
	 * 
	 */
	public KaajeeSSLHelper() {
		try {
			initialize();
		} catch (Exception e) {
			// already logged at lower level
		}
	}

	/**
	 * Update values (it's possible admin could dynamically change the server values for listen ports.)
	 * 
	 * @throws Exception
	 */
	public synchronized void updateValues() throws Exception {
		initialized=false;
		initialize();
	}

	private void initialize() throws Exception {

		try {
			MBeanServer localMBeanServer = getLocalMBeanServer();
			ObjectName oNamePartial = new ObjectName(JMX_DOMAIN_FOR_PLATFORM + ":Type=ServerRuntime,*");
			Set queryResults = localMBeanServer.queryNames(oNamePartial, null);
			if (queryResults.size() != 1) {
				logger.error("Expected 1 result for local server query, got: " + queryResults.size());
			} else {
				ObjectName serverObjectName = (ObjectName) queryResults.iterator().next();
				Integer lp = (Integer) localMBeanServer.getAttribute(serverObjectName, "ListenPort");
				ListenPort = lp.intValue();
				Integer sslP = (Integer) localMBeanServer.getAttribute(serverObjectName, "SSLListenPort");
				SSLListenPort = sslP.intValue();
				Boolean sslEnabled = (Boolean) localMBeanServer
						.getAttribute(serverObjectName, "SSLListenPortEnabled");
				SSLListenPortEnabled = sslEnabled.booleanValue();
			}
			initialized = true;
		} catch (MalformedObjectNameException e) {
			logger.error("Failure retrieving ServerRuntime MBean values: ", e);
			throw new Exception("Encountered problem while retrieving ServerRuntime MBean values", e);
		} catch (AttributeNotFoundException e) {
			logger.error("Failure retrieving ServerRuntime MBean values: ", e);
			throw new Exception("Encountered problem while retrieving ServerRuntime MBean values", e);
		} catch (InstanceNotFoundException e) {
			logger.error("Failure retrieving ServerRuntime MBean values: ", e);
			throw new Exception("Encountered problem while retrieving ServerRuntime MBean values", e);
		} catch (NullPointerException e) {
			logger.error("Failure retrieving ServerRuntime MBean values: ", e);
			throw new Exception("Encountered problem while retrieving ServerRuntime MBean values", e);
		} catch (MBeanException e) {
			logger.error("Failure retrieving ServerRuntime MBean values: ", e);
			throw new Exception("Encountered problem while retrieving ServerRuntime MBean values", e);
		} catch (ReflectionException e) {
			logger.error("Failure retrieving ServerRuntime MBean values: ", e);
			throw new Exception("Encountered problem while retrieving ServerRuntime MBean values", e);
		} catch (NamingException e) {
			// logged at lower level
			throw new Exception("Encountered problem while retrieving ServerRuntime MBean values", e);
		}

	}

	/**
	 * Gets listen port.
	 * 
	 * @return listen port for current server.
	 * @throws Exception
	 */
	public synchronized int getListenPort() throws Exception {
		if (!initialized) {
			initialize();
		}
		return ListenPort;
	}

	/**
	 * Gets SSL Listen port.
	 * 
	 * @return SSL listen port for current server.
	 * @throws Exception
	 */
	public synchronized int getSSLListenPort() throws Exception {
		if (!initialized) {
			initialize();
		}
		return SSLListenPort;
	}

	/**
	 * Gets whether SSL port is enabled.
	 * 
	 * @return whether SSL port is enabled on current server.
	 * @throws Exception
	 */
	public synchronized boolean isSSLListenPortEnabled() throws Exception {
		if (!initialized) {
			initialize();
		}
		return SSLListenPortEnabled;
	}

	/**
	 * Get the local, runtime MBeanServer for the current server.
	 * @return
	 * @throws NamingException
	 */
	private MBeanServer getLocalMBeanServer() throws NamingException {
		logger.debug("in getLocalMBeanServer");

		// method encapsulates access to localRuntimeMBeanServer instance variable for thread safety

		MBeanServer returnVal = null;
		Context ctx = null;
		try {
			ctx = new InitialContext();
			try {
				// if running at EJB-JAR or WAR level
				returnVal = (MBeanServer) ctx.lookup(MBEANSERVER_JNDI_ENV_CONTEXT);
			} catch (NameNotFoundException e) {
				// if running at EAR level
				try {
					returnVal = (MBeanServer) ctx.lookup(MBEANSERVER_JNDI_CONTEXT);
				} catch (NameNotFoundException e1) {
					logger.error("Could not retrieve local MBeanServer: ", e1);
					throw e;
				}
			}

		} catch (NamingException e) {
			logger.error("Could not get initial context: ", e);
			if (ctx != null) {
				try {
					ctx.close();
				} catch (NamingException e1) {
					logger.error("Could not close InitialContext: ", e1);
				}
			}
			throw e;
		} finally {
			try {
				ctx.close();
			} catch (NamingException e) {
				logger.error("error closing JNDI context", e);
			}
		}
		return returnVal;
	}
}
