package es.ejie.frmk.control.arranque;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Hashtable;

import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.ObjectName;

import es.ejie.frmk.Q70Factoria;
import es.ejie.frmk.control.excepciones.Q70FrameworkException;
import es.ejie.frmk.infraestructura.traza.Q70TraceLevel;
import es.ejie.frmk.infraestructura.traza.Q70TrazaInterface;
import es.ejie.frmk.infraestructura.utilidades.configurador.Q70Administrable;
import es.ejie.frmk.infraestructura.utilidades.configurador.Q70ConfigurableMBean;


public abstract class Q70MBeanBase extends NotificationBroadcasterSupport implements Q70MBeanInterface {

	private static final String DOMINIO_GEREMUA = "dominioGeremua";
	public static final String APPLICATION = "application";
	public static final String CLASS = "class";
	public static final String TYPE = "type";
	public static final String NAME = "name";
	private Q70Administrable claseComponente = null;
	private Q70ConfigurableMBean claseConfiguradora = null;
	private String aplicacion = null;
	private String nombreConfiguracion = null;
	private int contadorDesplegadosEnWars;
	private static long secuenciaNotificaciones = 0;
	private static final String COMPONENTE = "MBEANBASE";
		
	protected abstract void anadirComponenteFactoria();
	protected abstract void doConfiguracionComponente(Object configuracion) throws Q70FrameworkException;
	protected abstract void setComponenteAplicacion(Q70Administrable claseComponente);

	public Q70MBeanBase(){
		super();
		contadorDesplegadosEnWars = 1;
	}
	
	/**
	 * @see es.ejie.frmk.control.arranque.Q70Arrancable#afterCreate(java.lang.String)
	 */
	public void afterCreate(String xml) throws Q70FrameworkException {
		try {
			claseConfiguradora = doInstanciarConfiguracion(xml);	
			Object configuracion = getConfiguracionComponente();
			claseComponente.suspender();
			doConfiguracionComponente(configuracion);
			claseComponente.inicializar();
			anadirComponenteFactoria();
			claseComponente.activar();
			sendNotification(new Notification("afterCreate",this,++secuenciaNotificaciones,System.currentTimeMillis(),"resultado con éxito"));
		} catch (Exception e) {
			sendNotification(new Notification("afterCreate",this,++secuenciaNotificaciones,System.currentTimeMillis(),"resultado fracaso: "+e.getMessage()));
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método afterCreate de la clase Q70MBeanBase");
			}
			throw new Q70FrameworkException(COMPONENTE,this.getClass().toString(),"afterCreate","Se ha producido un error en el método afterCreate.",e);
		}

	}

	/**
	 * Obtiene una instancia de la clase configuradora a partir del fichero de configuración del componente
	 * @param xml fichero de configuración
	 * @return un objeto Q70ConfigurableMBean
	 * @throws Q70FrameworkException
	 */
	private Q70ConfigurableMBean doInstanciarConfiguracion(String xml) throws Q70FrameworkException{
		try {
			Class[] parametrosTipos = new Class[2];
			parametrosTipos[0] = String.class;
			parametrosTipos[1] = String.class;
			Constructor constructorConString = Class.forName(nombreConfiguracion).getConstructor(parametrosTipos);
			Object[] parametrosValores = new Object[2];
			parametrosValores[0] = xml;
			parametrosValores[1] = getAplicacion();
			claseConfiguradora = (Q70ConfigurableMBean)constructorConString.newInstance(parametrosValores);
		} catch (Throwable e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método doInstanciarConfiguracion de la clase Q70MBeanBase");
			}
			throw new Q70FrameworkException(COMPONENTE,this.getClass().toString(),"doInstanciarConfiguracion","Se ha producido un error en el método doInstanciarConfiguracion.",e);
		}

		return claseConfiguradora;
	}

	/**
	 * @see es.ejie.frmk.control.arranque.Q70Arrancable#beforeDestroy()
	 */
	public void beforeDestroy() throws Q70FrameworkException {
		try {
			claseComponente.suspender();
			claseComponente.destruir();
			claseComponente = null;
		} catch (Q70FrameworkException e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método beforeDestroy() de la clase Q70MBeanBase");
			}
			throw new Q70FrameworkException(COMPONENTE,this.getClass().toString(),"beforeDestroy","Se ha producido un error en el método beforeDestroy().",e);		}
	}

	/**
	 * @see es.ejie.frmk.control.arranque.Q70Arrancable#reConfigure()
	 */
	public void reConfigure() throws Q70FrameworkException {
		Object objConfiguracion = getConfiguracionComponente();	
		reConfigure(objConfiguracion);
	}
	
	/**
	 * @see es.ejie.frmk.control.arranque.Q70MBeanInterface#getConfiguracionComponente()
	 */
	public Object getConfiguracionComponente() throws Q70FrameworkException{
		try {
			return claseConfiguradora.recuperaConfiguracion();
		} catch (Q70FrameworkException e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método getConfiguracionComponente de la clase Q70MBeanBase");
			}
			throw new Q70FrameworkException(COMPONENTE,this.getClass().toString(),"getConfiguracionComponente","Se ha producido un error en el método getConfiguracionComponente.",e);
		}

	}

	/**
	 * Reconfigura un componente a partir de un objeto de configuración
	 * @param objConfiguracion objeto de configuración
	 * @throws Q70FrameworkException
	 */
	private void reConfigure(Object objConfiguracion) throws Q70FrameworkException {
		try {
			claseComponente.suspender();
			claseComponente.liberar();
			doConfiguracionComponente(objConfiguracion);
			claseComponente.inicializar();
			claseComponente.activar();
			claseConfiguradora.persisteConfiguracion(objConfiguracion);
			sendNotification(new Notification("reConfigure",this,++secuenciaNotificaciones,System.currentTimeMillis(),"resultado con éxito"));			
		} catch (Throwable e) {
			sendNotification(new Notification("reConfigure",this,++secuenciaNotificaciones,System.currentTimeMillis(),"resultado con fracaso: "+e.getMessage()));
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			String strNombreMBean = "";
			try {
				strNombreMBean = getNombreMBean().getCanonicalName();
			} catch (MalformedObjectNameException e1) {
				strNombreMBean = getClaseComponente().toString();
			}
			if (traza.isTraceActive()){
				traza.trace(COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método reConfigure(Object) al reconfigurar el componente: "+strNombreMBean);
			}
			throw new Q70FrameworkException(COMPONENTE,this.getClass().toString(),"reConfigure(Object)","Se ha producido un error al reconfigurar el componente: "+strNombreMBean,e);
		}
	}

	/**
	 * @see es.ejie.frmk.control.arranque.Q70MBeanInterface#reConfigure(java.lang.String)
	 */
	public void reConfigure(String xmlConfiguracion) throws Q70FrameworkException{
		try {
			doInstanciarConfiguracion(xmlConfiguracion);
			reConfigure();
		} catch (Throwable e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			String strNombreMBean = "";
			try {
				strNombreMBean = getNombreMBean().getCanonicalName();
			} catch (MalformedObjectNameException e1) {
				strNombreMBean = getClaseComponente().toString();
			}
			if (traza.isTraceActive()){
				traza.trace(COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método reConfigure(String) al reconfigurar el componente: "+strNombreMBean);
			}
			throw new Q70FrameworkException(COMPONENTE,this.getClass().toString(),"reConfigure(String)","Se ha producido un error al reconfigurar el componente: "+strNombreMBean,e);
		}
	}
	
	/**
	 * @see es.ejie.frmk.control.arranque.Q70MBeanInterface#setNombreComponente(java.lang.String)
	 */
	public void setNombreComponente(String nombreComponente) throws Q70FrameworkException, IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
		try {
			if (claseComponente != null){
				claseComponente.suspender();
				claseComponente.liberar();
			}	
			claseComponente = (Q70Administrable)(Class.forName(nombreComponente, true, Thread.currentThread().getContextClassLoader()).getMethod("getSingleton", null).invoke(null,null));
			claseComponente.activar();

			// TODO Posible mejora: el componente global pasa por aqui cuando no deberia hacerlo(extender la clase para aplicación...)
			setComponenteAplicacion(claseComponente);
		} catch (Throwable e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método setNombreComponente de la clase Q70MBeanBase");
			}
			throw new Q70FrameworkException(COMPONENTE,this.getClass().toString(),"setNombreComponente","Se ha producido un error en el método setNombreComponente.",e);
		}
	}

	/**
	 * @see es.ejie.frmk.control.arranque.Q70MBeanInterface#setNombreConfiguracion(java.lang.String)
	 */
	public void setNombreConfiguracion(String strNombreConfiguracion) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		nombreConfiguracion = strNombreConfiguracion;
	}

	/**
	 * @see es.ejie.frmk.control.arranque.Q70MBeanInterface#setAplicacion(java.lang.String)
	 */
	public void setAplicacion(String aplicacion){
		this.aplicacion = aplicacion;
	}
	
	/**
	 * Obtiene el código de aplicación
	 * @return un String
	 */
	protected String getAplicacion() {
		return aplicacion; 
	}
	
	/**
	 * @see es.ejie.frmk.control.arranque.Q70MBeanInterface#recuperaConfiguracionXML()
	 */
	public String recuperaConfiguracionXML() throws Q70FrameworkException {
		try {
			return claseConfiguradora.recuperaConfiguracionXML();
		} catch (Q70FrameworkException e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método recuperaConfiguracionXML de la clase Q70MBeanBase");
			}
			throw new Q70FrameworkException(COMPONENTE,this.getClass().toString(),"recuperaConfiguracionXML","Se ha producido un error en el método recuperaConfiguracionXML.",e);
		}
	}

	/**
	 * @see es.ejie.frmk.control.arranque.Q70MBeanInterface#persisteConfiguracionXML(java.lang.String)
	 */
	public void persisteConfiguracionXML(String xmlConfiguracion) throws Q70FrameworkException{
		try {
			claseConfiguradora.persisteConfiguracionXML(xmlConfiguracion);
			reConfigure();
		} catch (Throwable e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método persisteConfiguracionXML de la clase Q70MBeanBase");
			}
			throw new Q70FrameworkException(COMPONENTE,this.getClass().toString(),"persisteConfiguracionXML","Se ha producido un error en el método persisteConfiguracionXML.",e);
		}
	}

	/**
	 * Obtiene la clase del componente
	 * @return un objeto Q70Administrable
	 */
	protected Q70Administrable getClaseComponente() {
		return claseComponente;
	}
	
	/** 
	 * @see es.ejie.frmk.control.arranque.Q70MBeanInterface#incrementarContador()
	 */
	public void incrementarContador() {
		contadorDesplegadosEnWars++;
	}

	/**
	 * @see es.ejie.frmk.control.arranque.Q70MBeanInterface#decrementarContador()
	 */
	public void decrementarContador() {
		contadorDesplegadosEnWars--;
	}

	
	/**
	 * @see es.ejie.frmk.control.arranque.Q70MBeanInterface#esContadorCero()
	 */
	public boolean esContadorCero() {
		return contadorDesplegadosEnWars==0;
	}

	//**********************************************************************************
	// métodos referentes al ObjectName
	//**********************************************************************************
	
	/**
	 * Obtiene el nombre del mBean
	 * @param tipoClase tipo de la clase
	 * @param codAplicacion código de aplicación
	 * @return un objeto ObjectName
	 * @throws MalformedObjectNameException
	 */
	public static ObjectName getNombreMBean(String tipoClase, String codAplicacion) throws MalformedObjectNameException {
		return new ObjectName(DOMINIO_GEREMUA+":*,"+APPLICATION+"="+codAplicacion+","+CLASS+"="+tipoClase+","+TYPE+"=BeanComponente");
	}
	/**
	 * Obtiene el nombre del mBean
	 * @param tipoComponente tipo de componente
	 * @param tipoClase tipo de la clase
	 * @param codAplicacion código de aplicación
	 * @return un objeto ObjectName
	 * @throws MalformedObjectNameException
	 */
	public static ObjectName getNombreMBean(String tipoComponente, String tipoClase, String codAplicacion) throws MalformedObjectNameException {
		return new ObjectName(DOMINIO_GEREMUA+":*,"+APPLICATION+"="+codAplicacion+","+CLASS+"="+tipoClase+","+NAME+"="+tipoComponente+","+TYPE+"=BeanComponente");
	}

	/**
	 * Obtiene el nombre del mBean
	 * @param tipoComponente tipo de componente
	 * @param codAplicacion código de aplicación
	 * @return un objeto ObjectName
	 * @throws MalformedObjectNameException
	 */
	public static ObjectName getNombreMBeanAplicacion(String tipoComponente, String codAplicacion) throws MalformedObjectNameException {
		return new ObjectName(DOMINIO_GEREMUA+":*,"+APPLICATION+"="+codAplicacion+","+NAME+"="+tipoComponente+","+TYPE+"=BeanComponente");
	}
		
	/**
	 * Obtiene el nombre del mBean
	 * @param tipoComponente tipo de componente
	 * @return un objeto ObjectName
	 * @throws MalformedObjectNameException
	 */
	public static ObjectName getNombreMBean(String tipoComponente) throws MalformedObjectNameException {
		return new ObjectName(DOMINIO_GEREMUA+":*,"+NAME+"="+tipoComponente+","+TYPE+"=BeanComponente");
	}
	
	/**
	 * Obtiene un mapa de atributos del bean
	 * @param tipoComponente tipo de componente
	 * @param tipoClase tipo de clase
	 * @param codAplicacion código de aplicación
	 * @return un objeto Hashtable
	 */
	protected static Hashtable doMapaAtributos(String tipoComponente, String tipoClase, String codAplicacion) {
		Hashtable atributos = new Hashtable();
		atributos.put(NAME, tipoComponente);
		atributos.put(CLASS, tipoClase);
		atributos.put(TYPE, "BeanComponente");
		atributos.put(APPLICATION, codAplicacion);
		return atributos;
	}
	/**
	 * Obtiene el dominio del mBean
	 * @return un String
	 */
	public static String getDominioMBean(){
		return DOMINIO_GEREMUA;
	}

}
