/*
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Struts", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

package es.ejie.frmk.presentacion.iu.taglib.html;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.TryCatchFinally;

import org.apache.commons.beanutils.BasicDynaBean;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.struts.Globals;
import org.apache.struts.taglib.logic.IterateTag;
import org.apache.struts.util.MessageResources;
import org.apache.struts.util.RequestUtils;

import es.ejie.frmk.control.arranque.Q70GestorArranqueConfiguracion;
import es.ejie.frmk.control.excepciones.Q70GestorDeExcepciones;
import es.ejie.frmk.infraestructura.internacionalizacion.Q70Constantes;
import es.ejie.frmk.infraestructura.internacionalizacion.Q70GestorMensajesException;
import es.ejie.frmk.infraestructura.internacionalizacion.Q70GestorMensajesi18n;
import es.ejie.frmk.infraestructura.logs.incidencias.Q70LogIncidencias;
import es.ejie.frmk.infraestructura.seguridad.Q70GestorSeguridad;
import es.ejie.frmk.infraestructura.seguridad.Q70InformacionSeguridadUsuario;
import es.ejie.frmk.infraestructura.seguridad.Q70SeguridadException;
import es.ejie.frmk.infraestructura.traza.Q70TraceLevel;
import es.ejie.frmk.infraestructura.traza.Q70Traza;
import es.ejie.frmk.listeners.base.Q70ListenerUtils;
import es.ejie.frmk.presentacion.iu.taglib.base.Q70Constants;
import es.ejie.frmk.presentacion.iu.taglib.base.Q70CustomTagException;
import es.ejie.frmk.presentacion.sesiondatos.Q70ContextoEJIE;
	

/**
 * Clase que sirve como base a todos los custom-tags aportados por Geremua.
 * <p>
 * Esta clase es una adaptación de su homónima de Struts <code>org.apache.struts.taglib.html.BaseHandlerTag</code> para
 * que colabore con el Gestor de Excepciones, el Gestor de Mensajes e Internacionalización y la Traza.
 * <p>
 * Base class for tags that render form elements capable of including JavaScript
 * event handlers and/or CSS Style attributes. This class does not implement
 * the doStartTag() or doEndTag() methods. Subclasses should provide
 * appropriate implementations of these.
 *
 * @author Don Clasen
 * @author James Turner
 */

public abstract class Q70BaseHandlerTag 
	extends BodyTagSupport
	implements TryCatchFinally {

	/**
	 * Constante que indica el componente al que pertenece la clase. Es
	 * necesario para el uso del log de trazas.
	 */
	protected static final String COMPONENTE = Q70Constants.COMPONENTE_CUSTOM_TAG;

	/**
	 * Instancia del log de trazas
	 */
	private static Q70Traza tr = Q70Traza.getSingleton();
	/**
	 * Instancia del log de incidencias.
	 */
	private static Q70LogIncidencias logInc = Q70LogIncidencias.getSingleton();
	
	// ----------------------------------------------------- Instance Variables
	

	/**
	 * The default Locale for our server.
	 */
	protected static final Locale defaultLocale = Locale.getDefault();

	/**
	 * Recursos de i18n para mensajes internos de la clase.
	 */
	protected static MessageResources messages =
		MessageResources.getMessageResources(
			Q70Constants.Package + ".LocalStrings");


	//  Navigation Management

	/** Access key character. */
	protected String accesskey = null;

	/** Tab index value. */
	protected String tabindex = null;

	//  Indexing ability for Iterate

	/** Whether to created indexed names for fields
	  * @since Struts 1.1
	  */
	protected boolean indexed = false;
	
	protected ArrayList listaI18nKey = new ArrayList(3);



	// ------------------------------------------------ Atributos de Geremua  

	/**  Atributo para convertir el tag en un tag de tipo <code>hidden</code> */
	protected boolean hidden = false;

	/**  Devuelve el atributo de los ficheros javaScript que conforman un menu  del tag */
	private String secureId = null;
	
	/** Establece el atributo de los ficheros javaScript que conforman un menu  del tag*/
	private String javaScriptFiles = null;
	
	/**
	 * Clave de acceso del mensaje a recuperar 
	 * por el componente Gestor de Mensajes e Internacionalizacion.
	 * Por defecto, se inicializa con el fichero de recursos de Geremua
	 */
	private String i18nKey = null;
	
	//  Mouse Events

	/** Mouse click event. */
	private String onclick = null;

	/** Mouse double click event. */
	private String ondblclick = null;

	/** Mouse over component event. */
	private String onmouseover = null;

	/** Mouse exit component event. */
	private String onmouseout = null;

	/** Mouse moved over component event. */
	private String onmousemove = null;

	/** Mouse pressed on component event. */
	private String onmousedown = null;

	/** Mouse released on component event. */
	private String onmouseup = null;

	//  Keyboard Events

	/** Key down in component event. */
	private String onkeydown = null;

	/** Key released in component event. */
	private String onkeyup = null;

	/** Key down and up together in component event. */
	private String onkeypress = null;

	// Text Events

	/** Text selected in component event. */
	private String onselect = null;

	/** Content changed after component lost focus event. */
	private String onchange = null;

	// Focus Events and States

	/** Component lost focus event. */
	private String onblur = null;

	/** Component has received focus event. */
	private String onfocus = null;

	/** Component is disabled. */
	private boolean disabled = false;

	/** Component is readonly. */
	private boolean readonly = false;

	// CSS Style Support

	/** Style attribute associated with component. */
	private String style = null;

	/** Named Style class associated with component. */
	private String styleClass = null;

	/** Identifier associated with component.  */
	private String styleId = null;

	// Other Common Attributes

	/** The alternate text of this element. */
	private String alt = null;

	/** The message resources key of the alternate text. */
	private String altKey = null;

	/** The name of the session attribute key for our locale. */
	private String locale = Globals.LOCALE_KEY;

	/** The advisory title of this element. */
	private String title = null;

	/** The message resources key of the advisory title. */
	private String titleKey = null;

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

	//  Navigation Management

	/** Sets the accessKey character. */
	public void setAccesskey(String accessKey) {
		this.accesskey = accessKey;
	}

	/** Returns the accessKey character. */
	public String getAccesskey() {
		return (this.accesskey);
	}

	/** Sets the tabIndex value. */
	public void setTabindex(String tabIndex) {
		this.tabindex = tabIndex;
	}

	/** Returns the tabIndex value. */
	public String getTabindex() {
		return (this.tabindex);
	}

	//  Indexing ability for Iterate [since Struts 1.1]

	/** Sets the indexed value.
	  * @since Struts 1.1
	  */
	public void setIndexed(boolean indexed) {
		this.indexed = indexed;
	}

	/** Returns the indexed value.
	  * @since Struts 1.1
	  */
	public boolean getIndexed() {
		return (this.indexed);
	}

	// Mouse Events

	/** Sets the onClick event handler. */
	public void setOnclick(String onClick) {
		this.onclick = onClick;
	}

	/** Returns the onClick event handler. */
	public String getOnclick() {
		return onclick;
	}

	/** Sets the onDblClick event handler. */
	public void setOndblclick(String onDblClick) {
		this.ondblclick = onDblClick;
	}

	/** Returns the onDblClick event handler. */
	public String getOndblclick() {
		return ondblclick;
	}

	/** Sets the onMouseDown event handler. */
	public void setOnmousedown(String onMouseDown) {
		this.onmousedown = onMouseDown;
	}

	/** Returns the onMouseDown event handler. */
	public String getOnmousedown() {
		return onmousedown;
	}

	/** Sets the onMouseUp event handler. */
	public void setOnmouseup(String onMouseUp) {
		this.onmouseup = onMouseUp;
	}

	/** Returns the onMouseUp event handler. */
	public String getOnmouseup() {
		return onmouseup;
	}

	/** Sets the onMouseMove event handler. */
	public void setOnmousemove(String onMouseMove) {
		this.onmousemove = onMouseMove;
	}

	/** Returns the onMouseMove event handler. */
	public String getOnmousemove() {
		return onmousemove;
	}

	/** Sets the onMouseOver event handler. */
	public void setOnmouseover(String onMouseOver) {
		this.onmouseover = onMouseOver;
	}

	/** Returns the onMouseOver event handler. */
	public String getOnmouseover() {
		return onmouseover;
	}

	/** Sets the onMouseOut event handler. */
	public void setOnmouseout(String onMouseOut) {
		this.onmouseout = onMouseOut;
	}

	/** Returns the onMouseOut event handler. */
	public String getOnmouseout() {
		return onmouseout;
	}

	// Keyboard Events

	/** Sets the onKeyDown event handler. */
	public void setOnkeydown(String onKeyDown) {
		this.onkeydown = onKeyDown;
	}

	/** Returns the onKeyDown event handler. */
	public String getOnkeydown() {
		return onkeydown;
	}

	/** Sets the onKeyUp event handler. */
	public void setOnkeyup(String onKeyUp) {
		this.onkeyup = onKeyUp;
	}

	/** Returns the onKeyUp event handler. */
	public String getOnkeyup() {
		return onkeyup;
	}

	/** Sets the onKeyPress event handler. */
	public void setOnkeypress(String onKeyPress) {
		this.onkeypress = onKeyPress;
	}

	/** Returns the onKeyPress event handler. */
	public String getOnkeypress() {
		return onkeypress;
	}

	// Text Events

	/** Sets the onChange event handler. */
	public void setOnchange(String onChange) {
		this.onchange = onChange;
	}

	/** Returns the onChange event handler. */
	public String getOnchange() {
		return onchange;
	}

	/** Sets the onSelect event handler. */
	public void setOnselect(String onSelect) {
		this.onselect = onSelect;
	}

	/** Returns the onSelect event handler. */
	public String getOnselect() {
		return onselect;
	}

	// Focus Events and States

	/** Sets the onBlur event handler. */
	public void setOnblur(String onBlur) {
		this.onblur = onBlur;
	}

	/** Returns the onBlur event handler. */
	public String getOnblur() {
		return onblur;
	}

	/** Sets the onFocus event handler. */
	public void setOnfocus(String onFocus) {
		this.onfocus = onFocus;
	}

	/** Returns the onFocus event handler. */
	public String getOnfocus() {
		return onfocus;
	}

	/** Sets the disabled event handler. */
	public void setDisabled(boolean disabled) {
		this.disabled = disabled;
	}

	/** Returns the disabled event handler. */
	public boolean getDisabled() {
		return disabled;
	}

	/** Sets the readonly event handler. */
	public void setReadonly(boolean readonly) {
		this.readonly = readonly;
	}

	/** Returns the readonly event handler. */
	public boolean getReadonly() {
		return readonly;
	}

	// CSS Style Support

	/** Sets the style attribute. */
	public void setStyle(String style) {
		this.style = style;
	}

	/** Returns the style attribute. */
	public String getStyle() {
		return style;
	}

	/** Sets the style class attribute. */
	public void setStyleClass(String styleClass) {
		this.styleClass = styleClass;
	}

	/** Returns the style class attribute. */
	public String getStyleClass() {
		return styleClass;
	}

	/** Sets the style id attribute.  */
	public void setStyleId(String styleId) {
		this.styleId = styleId;
	}

	/** Returns the style id attribute.  */
	public String getStyleId() {
		return styleId;
	}

	// Other Common Elements

	/** Returns the alternate text attribute. */
	public String getAlt() {
		return alt;
	}

	/** Sets the alternate text attribute. */
	public void setAlt(String alt) {
		this.alt = alt;
	}

	/** Returns the message resources key of the alternate text. */
	public String getAltKey() {
		return altKey;
	}

	/** Sets the message resources key of the alternate text. */
	public void setAltKey(String altKey) {
		this.altKey = altKey;
	}

	/** Returns the name of the session attribute for our locale. */
	public String getLocale() {
		return locale;
	}

	/** Sets the name of the session attribute for our locale. */
	public void setLocale(String locale) {
		this.locale = locale;
	}

	/** Returns the advisory title attribute. */
	public String getTitle() {
		return title;
	}

	/** Sets the advisory title attribute. */
	public void setTitle(String title) {
		this.title = title;
	}

	/** Returns the message resources key of the advisory title. */
	public String getTitleKey() {
		return titleKey;
	}

	/** Sets the message resources key of the advisory title. */
	public void setTitleKey(String titleKey) {
		this.titleKey = titleKey;
	}
	
	
	
	/** Devuelve el valor del atributo hidden del tag */
	public boolean getHidden() {
		return hidden;
	}

	/** Establece el valor del atributo hidden del tag */
	public void setHidden(boolean hidden) {
		this.hidden = hidden;
	}


	/** Devuelve el valor del atributo secureId del tag */
	public String getSecureId() {
		return secureId;
	}

	/** Establece el valor del atributo secureId del tag */
	public void setSecureId(String secureId) {
		this.secureId = secureId;
	}
	
	/**
	 * Devuelve los ficheros javaScripts
	 * @return String
	 */
	public String getJavaScriptFiles(){
    	return javaScriptFiles;
    }
    
	
    /**
     * Se inicializa el fichero javaScript
     * @param javaScriptFiles
     */
    public void setJavaScriptFiles(String javaScriptFiles) {
    	this.javaScriptFiles=javaScriptFiles;
    }
	
	
	/** 
	 * Este método construye la lista jerarquica de ambitos de internacionalización para el tag.
	 * <br>La lista jerarquica se contruye con los atributos i18nKey siguientes de tal forma:
	 * 
	 * <ul>
	 * <li>1. Ámbito definido en el atributo "i18nKey" del Tag propio (input, text, msg, …).</li>
	 * <li>2. Ámbito definido en el atributo "i18nKey" del Tag del formulario (form).</li>
	 * <li>3. Ámbito definido en el atributo "i18nKey" del Tag de html (html).</li>
	 * <li>4. Ámbito por defecto de Geremua ("framework").</li>
	 * </ul>
	 * 
	 * <br>El atributo "i18nKey" es opcional en los Tags, por tanto, 
	 * si no se ha definido en algún Tag de los expuestos en esta jerarquía, 
	 * entonces no se añade al Arbol de Búsqueda de Ambitos.
	 * 
	 * @return Lista jerarquica de ambitos de internacionalización para el tag.
	 */
	public List getListaI18nKey() 
	{				
		if (!listaI18nKey.isEmpty()){
			return listaI18nKey;
		}

		if (null != getI18nKey()){		
			listaI18nKey.add(getI18nKey());				
		}
		Object savedI18nKey = pageContext.getAttribute(Q70Constants.USE_MENU_DISPLAYER_I18N_KEY, PageContext.REQUEST_SCOPE);
		if (null != savedI18nKey){
			listaI18nKey.add(savedI18nKey);				
		}

		savedI18nKey = pageContext.getAttribute(Q70Constants.FORM_I18N_KEY, PageContext.REQUEST_SCOPE);
		if (null != savedI18nKey){
			listaI18nKey.add(savedI18nKey);				
		}
		savedI18nKey = pageContext.getAttribute(Q70Constants.HTML_I18N_KEY, PageContext.REQUEST_SCOPE);
		if (null != savedI18nKey){			
			listaI18nKey.add(savedI18nKey);				
		}
		
		if (listaI18nKey.isEmpty()){
			listaI18nKey.add(Q70Constantes.FICHERO_PROPIEDADES); //Fichero de recursos de Geremua (o por defecto)				
		}
	
		return listaI18nKey;
	}
	
	 /**
	 * Este método establece la lista jerarquica de ambitos i18nKey para el tag.
	 * 
	 * @param listai18nkey Lista jerarquica de ambitos i18nKey.
	 */
	public void setListaI18nKey (List listai18nkey){
		 this.listaI18nKey = (ArrayList)listai18nkey;
	 }
		
	/**
	 * Este método obtiene el valor del atributo i18nKey del tag.
	 * 
	 * @return Atributo i18nKey del tag 
	 */
	public String getI18nKey() {
		return i18nKey;
	}
	
	/**
	 * Establece el valor del atributo i18nKey del tag
	 * 
	 * @param key Atributo i18nKey del tag
	 */
	public void setI18nKey(String key) {
		this.i18nKey = key;
	}


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

	/**
	 * Release any acquired resources.
	 */
	public void release() {

		super.release();
		accesskey = null;
		alt = null;
		altKey = null;
		indexed = false;
		locale = Globals.LOCALE_KEY;
		onclick = null;
		ondblclick = null;
		onmouseover = null;
		onmouseout = null;
		onmousemove = null;
		onmousedown = null;
		onmouseup = null;
		onkeydown = null;
		onkeyup = null;
		onkeypress = null;
		onselect = null;
		onchange = null;
		onblur = null;
		onfocus = null;
		disabled = false;
		readonly = false;
		style = null;
		styleClass = null;
		styleId = null;
		tabindex = null;
		title = null;
		titleKey = null;
		javaScriptFiles=null;
	}

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

	/**
	 * Este método colabora con el Gestor de Mensajes e i18n para devolver un texto internacionalizado.
	 * <p>  
	 * @param literal Valor del texto o <code>null</code>
	 * @param key Clave de internacionalización o <code>null</code>
	 * @return message Valor del texto o descripción asociada a la Clave de Internacionalización
	 * @exception JspException si ambos argumentos son no <code>null<code>
	 */
	protected String message(String literal, String key) throws JspException 
	{
		if (literal != null) {
			if (key != null) {
				JspException e =
					new JspException(messages.getMessage("common.both"));
				RequestUtils.saveException(pageContext, e);
				throw e;
			} else {
				return (literal);
			}
		} 
		else 
		{
			if (key != null) {
				//Obtener el mensaje llamando al Gestor de Mensajes e Internacionalizacion
				return (
					getI18nMessage(getListaI18nKey(), 
							   	   key, 
							       getLocale(), 
							       null) ); 
			} else {
				return null;
			}
		}

	}

	private Class loopTagSupportClass = null;
	private Method loopTagSupportGetStatus = null;
	private Class loopTagStatusClass = null;
	private Method loopTagStatusGetIndex = null;
	private boolean triedJstlInit = false;
	private boolean triedJstlSuccess = false;

	private Integer getJstlLoopIndex() 
	{
		if (!triedJstlInit) {
			triedJstlInit = true;
			try {
				loopTagSupportClass =
					RequestUtils.applicationClass(
						"javax.servlet.jsp.jstl.core.LoopTagSupport");
				loopTagSupportGetStatus =
					loopTagSupportClass.getDeclaredMethod(
						"getLoopStatus",
						null);
				loopTagStatusClass =
					RequestUtils.applicationClass(
						"javax.servlet.jsp.jstl.core.LoopTagStatus");
				loopTagStatusGetIndex =
					loopTagStatusClass.getDeclaredMethod("getIndex", null);
				triedJstlSuccess = true;
			}
			// These just mean that JSTL isn't loaded, so ignore
			catch (ClassNotFoundException ex) {
			} catch (NoSuchMethodException ex) {
			}
		}
		if (triedJstlSuccess) {
			try {
				Object loopTag =
					findAncestorWithClass(this, loopTagSupportClass);
				if (loopTag == null) {
					return null;
				}
				Object status = loopTagSupportGetStatus.invoke(loopTag, null);
				return (Integer) loopTagStatusGetIndex.invoke(status, null);
			} catch (IllegalAccessException ex) {
				if (logInc.isEnabled())	logInc.log(ex, COMPONENTE, ex.getMessage());
			} catch (IllegalArgumentException ex) {
				if (logInc.isEnabled())	logInc.log(ex, COMPONENTE, ex.getMessage());
			} catch (InvocationTargetException ex) {
				if (logInc.isEnabled())	logInc.log(ex, COMPONENTE, ex.getMessage());
			} catch (NullPointerException ex) {
				if (logInc.isEnabled())	logInc.log(ex, COMPONENTE, ex.getMessage());
			} catch (ExceptionInInitializerError ex) {
				if (logInc.isEnabled())	logInc.log(ex, COMPONENTE, ex.getMessage());
			}
			
		}
		return null;
	}

	/**
	 *  Appends bean name with index in brackets for tags with
	 *  'true' value in 'indexed' attribute.
	 *  @param handlers The StringBuffer that output will be appended to.
	 *  @exception JspException if 'indexed' tag used outside of iterate tag.
	 */
	protected void prepareIndex(StringBuffer handlers, String name)
		throws JspException {
		int index = 0;
		boolean found = false;

		// look for outer iterate tag
		IterateTag iterateTag =
			(IterateTag) findAncestorWithClass(this, IterateTag.class);
		// Look for JSTL loops
		if (iterateTag == null) {
			Integer i = getJstlLoopIndex();
			if (i != null) {
				index = i.intValue();
				found = true;
			}
		} else {
			index = iterateTag.getIndex();
			found = true;
		}
		if (!found) {
			// this tag should only be nested in iteratetag, if it's not, throw exception
			JspException e =
				new JspException(
					messages.getMessage("indexed.noEnclosingIterate"));
			RequestUtils.saveException(pageContext, e);
			throw e;
		}
		if (name != null)
			handlers.append(name);
		handlers.append("[");
		handlers.append(index);
		handlers.append("]");
		if (name != null)
			handlers.append(".");
	}

	/**
	 * Prepares the style attributes for inclusion in the component's HTML tag.
	 * @return The prepared String for inclusion in the HTML tag.
	 * @exception JspException if invalid attributes are specified
	 */
	protected String prepareStyles() throws JspException {
		String value = null;
		StringBuffer styles = new StringBuffer();
		if (style != null) {
			styles.append(" style=\"");
			styles.append(getStyle());
			styles.append("\"");
		}
		if (styleClass != null) {
			styles.append(" class=\"");
			styles.append(getStyleClass());
			styles.append("\"");
		}
		if (styleId != null) {
			styles.append(" id=\"");
			styles.append(getStyleId());
			styles.append("\"");
		}
		value = message(title, titleKey);
		if (value != null) {
			styles.append(" title=\"");
			styles.append(value);
			styles.append("\"");
		}
		value = message(alt, altKey);
		if (value != null) {
			styles.append(" alt=\"");
			styles.append(value);
			styles.append("\"");
		}
		return styles.toString();
	}

	/**
	 * Prepares the event handlers for inclusion in the component's HTML tag.
	 * @return The prepared String for inclusion in the HTML tag.
	 */
	protected String prepareEventHandlers() {
		StringBuffer handlers = new StringBuffer();
		prepareMouseEvents(handlers);
		prepareKeyEvents(handlers);
		prepareTextEvents(handlers);
		prepareFocusEvents(handlers);
		return handlers.toString();
	}

	/**
	 * Prepares the mouse event handlers, appending them to the the given
	 * StringBuffer.
	 * @param handlers The StringBuffer that output will be appended to.
	 */
	protected void prepareMouseEvents(StringBuffer handlers) {
		if (onclick != null) {
			handlers.append(" onclick=\"");
			handlers.append(getOnclick());
			handlers.append("\"");
		}

		if (ondblclick != null) {
			handlers.append(" ondblclick=\"");
			handlers.append(getOndblclick());
			handlers.append("\"");
		}

		if (onmouseover != null) {
			handlers.append(" onmouseover=\"");
			handlers.append(getOnmouseover());
			handlers.append("\"");
		}

		if (onmouseout != null) {
			handlers.append(" onmouseout=\"");
			handlers.append(getOnmouseout());
			handlers.append("\"");
		}

		if (onmousemove != null) {
			handlers.append(" onmousemove=\"");
			handlers.append(getOnmousemove());
			handlers.append("\"");
		}

		if (onmousedown != null) {
			handlers.append(" onmousedown=\"");
			handlers.append(getOnmousedown());
			handlers.append("\"");
		}

		if (onmouseup != null) {
			handlers.append(" onmouseup=\"");
			handlers.append(getOnmouseup());
			handlers.append("\"");
		}
	}

	/**
	 * Prepares the keyboard event handlers, appending them to the the given
	 * StringBuffer.
	 * @param handlers The StringBuffer that output will be appended to.
	 */
	protected void prepareKeyEvents(StringBuffer handlers) {

		if (onkeydown != null) {
			handlers.append(" onkeydown=\"");
			handlers.append(getOnkeydown());
			handlers.append("\"");
		}

		if (onkeyup != null) {
			handlers.append(" onkeyup=\"");
			handlers.append(getOnkeyup());
			handlers.append("\"");
		}

		if (onkeypress != null) {
			handlers.append(" onkeypress=\"");
			handlers.append(getOnkeypress());
			handlers.append("\"");
		}
	}

	/**
	 * Prepares the text event handlers, appending them to the the given
	 * StringBuffer.
	 * @param handlers The StringBuffer that output will be appended to.
	 */
	protected void prepareTextEvents(StringBuffer handlers) {

		if (onselect != null) {
			handlers.append(" onselect=\"");
			handlers.append(getOnselect());
			handlers.append("\"");
		}

		if (onchange != null) {
			handlers.append(" onchange=\"");
			handlers.append(getOnchange());
			handlers.append("\"");
		}
	}

	/**
	 * Prepares the focus event handlers, appending them to the the given
	 * StringBuffer.
	 * @param handlers The StringBuffer that output will be appended to.
	 */
	protected void prepareFocusEvents(StringBuffer handlers) {

		if (onblur != null) {
			handlers.append(" onblur=\"");
			handlers.append(getOnblur());
			handlers.append("\"");
		}

		if (onfocus != null) {
			handlers.append(" onfocus=\"");
			handlers.append(getOnfocus());
			handlers.append("\"");
		}

		if (disabled) {
			handlers.append(" disabled=\"disabled\"");
		}

		if (readonly) {
			handlers.append(" readonly=\"readonly\"");
		}

	}

	/**
	 * Allows HTML tags to find out if they're nested within an %lt;html:html&gt; tag that
	 * has xhtml set to true.
	 * @return true if the tag is nested within an html tag with xhtml set to true, false
	 * otherwise.
	 * @since Struts 1.1
	 */
	protected boolean isXhtml() {
		String xhtml =
			(String) this.pageContext.getAttribute(
				Globals.XHTML_KEY,
				PageContext.PAGE_SCOPE);

		if ("true".equalsIgnoreCase(xhtml)) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Returns the closing brace for an input element depending on xhtml status.  The tag
	 * must be nested within an %lt;html:html&gt; tag that has xhtml set to true.
	 * @return String - &gt; if xhtml is false, /&gt; if xhtml is true
	 * @since Struts 1.1
	 */
	protected String getElementClose() {
		if (this.isXhtml()) {
			return " />";
		} else {
			return ">";
		}
	}

	/**
	 * Searches all scopes for the bean and calls BeanUtils.getProperty() with the 
	 * given arguments and converts any exceptions into JspException.
	 * 
	 * @param beanName The name of the object to get the property from.
	 * @param property The name of the property to get.
	 * @return The value of the property.
	 * @throws JspException
	 * @since Struts 1.1
	 */
	protected String lookupProperty(String beanName, String property)
		throws JspException {

		Object bean = RequestUtils.lookup(this.pageContext, beanName, null);
		if (bean == null) {
			JspException e =
				new JspException(messages.getMessage("getter.bean", beanName));
			RequestUtils.saveException(pageContext, e);
			throw e;				
		}

		try {
			return BeanUtils.getProperty(bean, property);

		} catch (IllegalAccessException e) {
            RequestUtils.saveException(pageContext, e);
			throw new JspException(
				messages.getMessage("getter.access", property, beanName), e);

		} catch (InvocationTargetException e) {
			Throwable t = e.getTargetException();
            RequestUtils.saveException(pageContext, t);
			throw new JspException(
				messages.getMessage("getter.result", property, t.toString()),t);

		} catch (NoSuchMethodException e) {
            RequestUtils.saveException(pageContext, e);
			throw new JspException(
				messages.getMessage("getter.method", property, beanName),e);
		}
	}


	//------------------------------------------------------------------------------------- GEREMUA METHODS

	/**
	 * Devuelve el modo de display del tag (MODE_READWRITE|MODE_READWRITE|MODE_READONLY), el cual modifica en tiempo
	 * de renderización del tag (tiempo de ejecución) el aspecto del mismo .
	 * <p>
	 * Para ello colabora con el Gestor de Seguridad para que imponga restricciones de modo de display si fuera necesario.
	 * <p>
	 * @return <code>displayMode</code> Modo de display del tag. 
	 */
	public int getDisplayMode() throws JspException {
		int displayMode = Q70Constants.MODE_READWRITE;
				
		// Obtención del display mode desde el pto de vista de seguridad 
		int securityDisplayMode = getSecurityDisplayMode();
		
		if (tr.isTraceActive()) {		
			tr.trace(COMPONENTE, Q70TraceLevel.INFO, "El Modo de display segun Seguridad del tag " + this.getClass().getName() + " y secureId= "+ secureId +" es: " + securityDisplayMode);
		}
		
		// Toma de decisión de modo de display según prioridades
		boolean notDisplayed = false;
							
		if (hidden || securityDisplayMode == Q70Constants.MODE_NOTDISPLAYED) {
			notDisplayed = true;
		}		
		boolean readOnly = false;		
		if (!notDisplayed && (disabled || securityDisplayMode == Q70Constants.MODE_READONLY) ) {
			readOnly  = true;			
		}		

		if (notDisplayed) {
			displayMode = Q70Constants.MODE_NOTDISPLAYED;
		} else {
			if (readOnly) {
				displayMode = Q70Constants.MODE_READONLY;		
			} 
		}
		
		if (tr.isTraceActive())	tr.trace(COMPONENTE, Q70TraceLevel.INFO, "El modo de display final del tag " + this.getClass().toString() + " es: " + displayMode);
		
		return displayMode; 
	}


	/**
	 * Este método convierte ocurrencias de valores de elementos de tipo lista a custom-tags de tipo hidden.
	 * <P>
	 * Escribe tantos tags de tipo "hidden" como elementos tenga la lista de valores "values". Se utiliza cuando 
	 * el tag a renderizar tiene múltiples valores (por ejemplo, una lista desplegable) y el atributo "hidden" = true o
	 * bien el Gestor de Seguridad impone una restricción que le obliga a renderizarse en modo MODE_NOTDISPLAYED.
	 * <p> 
	 * @param (String)property: valor a asignar al atributo "name" del tag a escribir.
	 * @param (String[])values: lista con los valores a asignar al atributo "value" del tag a escribir.
	 * @throws JspException
	 */
	protected void writeHiddenTag (String property, String[] values) throws JspException 
	{
		if (null != property && property.length()>0) {
						
			int valuesSize = (null!=values ? values.length : 1);

			// Pintar un tag hidden por cada elemento del array values[]
			for (int i=0; i < valuesSize; i++) {					
				Q70HiddenTag hidden = new Q70HiddenTag();							
				hidden.setWrite(false);
				hidden.setPageContext(this.pageContext);							
				hidden.setProperty( (property!=null?property:" sys") );      //Asigna valor al atributo "name" del hidden			
				hidden.setValue( (values[i]!=null?values[i]:"") );			
				hidden.doStartTag();			
			} 		
				
		} 
		
	}



	/**
  	* Este método devuelve un texto asociado a un idioma, un ámbito y una clave de internacionalización.
  	* <p>
  	* Para ello colabora con el componente Gestor de Mensajes e Internacionalizacion. Previo a hacer uso del idioma
  	* enviado como argumento, se investiga si la aplicación tiene una política de idioma global, en cuyo caso, se ignorará
  	* el idioma enviado.
  	* <p>
  	* @param (PageContext) pageContext: contexto de página del tag
  	* @param (List) i18nKeyLista: lista de ámbitos donde se busca el mensaje
  	* @param (String) language: lenguaje del mensaje a obtener
	* @param (String) key: valor de la clave del mensaje
	* @param (Object[]) args: argumentos a reemplazar en el mensaje
	* @return texto del mensaje
	* @throws JspException
	*/
	protected String getI18nMessage(List i18nKeyLista, 
								 String key, 
								 String language, 
								 Object[] args) 	
		throws JspException 
	{
		String msg = null;	
		
		try
		{	

			//Obtener (si existe) el idioma por defecto de aplicacion definido por configuracion,
			// y usarlo en lugar del recibido por parametro.
			String globalLanguage = 
						Q70ListenerUtils.getLanguage((HttpServletRequest)pageContext.getRequest());

			if (null!=globalLanguage)
				language = globalLanguage;

			if (tr.isTraceActive())	
			{
				tr.trace(COMPONENTE, Q70TraceLevel.DEBUG, "Llamando al Gestor de Mensajes e Internacionalizacion con valores: i18nKey=" + i18nKeyLista + ",key="+key+",lenguaje="+language);
			}					
			
			
			//Llamar al Gestor de Mensajes e Internacionalizacion
			msg = Q70GestorMensajesi18n.getSingleton().getMessage(i18nKeyLista, key, language, args);
							
		}
		catch (Q70GestorMensajesException gme)
		{
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Excepcion capturada al llamar al Gestor de Mensajes e Internacionalizacion con valores: i18nKey=" + i18nKeyLista + ",key="+key+",lenguaje="+language);
			}
			
			RequestUtils.saveException(pageContext, gme);
			throw new JspException(gme);			
		}
		

		if (tr.isTraceActive())	
		{
			tr.trace(COMPONENTE, Q70TraceLevel.DEBUG, "El resultado de la llamada al Gestor de Mensajes e Internacionalizacion es: " + (msg!=null ? msg : "null"));
		}

		return msg;

	}

	  /**
	  * @param bean Bean whose property is to be extracted
	  * @param name Name of the property to be extracted
	  *
	  * @exception IllegalAccessException if the caller does not have
	  *  access to the property accessor method
	  * @exception InvocationTargetException if the property accessor method
	  *  throws an exception
	  * @exception NoSuchMethodException if an accessor method for this
	  *  property cannot be found
	  */
	 public String[] getArrayProperty(Object bean, String name)
			 throws IllegalAccessException, InvocationTargetException,
			 NoSuchMethodException {

		 Object value = PropertyUtils.getProperty(bean, name);
		 if (value == null) {
			 return (null);
		 } else if (value instanceof Collection) {
			 ArrayList values = new ArrayList();
			 Iterator items = ((Collection) value).iterator();
			 while (items.hasNext()) {
				 Object item = items.next();
				 if (item == null) {
					 values.add((String) null);
				 } else {
					 //values.add(item.toString());
					 values.add(getString(item));
				 }
			 }
			 return ((String[]) values.toArray(new String[values.size()]));
		 } else if (value.getClass().isArray()) {
			 int n = Array.getLength(value);
			 String results[] = new String[n];
			 for (int i = 0; i < n; i++) {
				 Object item = Array.get(value, i);
				 if (item == null) {
					 results[i] = null;
				 } else {
					 //results[i] = item.toString();
					results[i] = getString(item);
				 }
			 }
			 return (results);		 
		 } else {
			 String results[] = new String[1];
			 //results[0] = value.toString();
			 results[0] = getString(value);			 
			 return (results);
		 }
	 }


	/**
	 * El método devuelve el contador de objetos "bridge" de la página.
	 * <p>
	 * Los objetos "bridge" son iframes de HTML que establecen comunicaciones en tiempo de ejecución con una clase 
	 * servidora ("Servlet"), invocándola y quedando a la espera síncronamente hasta que ésta responda. 
	 * Se necesita mantener un contador de los objetos "bridge" que genera la página, para poder obtener dinámicamente
	 * los identificadores de los <b>iframes</b> necesarios para el retorno de valores desde el Servlet.
	 * <p>
	 * Los Servlets invocados desde los iframes son típicamente la parte servidora de los componentes <b>Gestor de Validaciones Remotas</b> y
	 * <b>Gestor de Listas Enlazadas</b>. 
	 * 
	 * @return (int): valor del contador de bridges
	 */
	public int getBridgeCounter()
	{
		int counter = 0;
		
		
		if (null!=pageContext)
		{
			Object bcounter = pageContext.getAttribute(Q70Constants.BRIDGE_COUNTER, PageContext.REQUEST_SCOPE);
			if (null!=bcounter && 0 < ((Integer) bcounter).intValue())
			{
				counter = ((Integer) bcounter).intValue();
			}
		}
						
		if (tr.isTraceActive())	
		{
			tr.trace(COMPONENTE, Q70TraceLevel.DEBUG, "El valor del contador de objetos bridge de la pagina es: " + counter);
		}		
		
		return counter;
	}
	/**
	 * El método actualiza en el contexto de la request el contador de objetos Bridge generados por página.
	 * <p>
	 *   
	 */
	public void putBridgeCounter()
	{
		if (null!=pageContext) {
			pageContext.setAttribute(Q70Constants.BRIDGE_COUNTER, new Integer(getBridgeCounter()+1), PageContext.REQUEST_SCOPE);
		}
	}	

	/**
	 * Método que deja una marca de paso en el pageContext.
	 * @param token marca de paso
	 */
	public void putInPageContext(String token) {
		if (null!=pageContext && null!=token)
			pageContext.setAttribute(token, "TRUE", PageContext.REQUEST_SCOPE);
	}
	
	/**
	 * Método que indica si existe una marca de paso en el pageContext.
	 * @param token marca de paso
	 * @return boolean
	 */
	public boolean isInPageContext(String token) {
		if (null!=pageContext && null!=token) {
			return "TRUE".equals(pageContext.getAttribute(token, PageContext.REQUEST_SCOPE));
		} else {
			return false;
		}
	}
	
	/**
	 * El método genera los nombres para objetos "bridge".
	 * <p>
	 * Obtiene el nombre a asignar a un objeto "bridge" (o <b>iframe</b> que se comunica con un Servlet) como resultado 
	 * de concatenar una constante al contador de objetos "bridge" generados en la página.  
	 * @return (String): nombre del iframe
	 */
	public String getBridgeName(){
		
		StringBuffer iframeName = new StringBuffer(256);

		iframeName.append(Q70Constants.BRIDGE_NAME)
				  //.append(Math.random())
				  .append(getBridgeCounter());

		if (tr.isTraceActive())	
		{
			tr.trace(COMPONENTE, Q70TraceLevel.DEBUG, "El nombre del objeto bridge es: " + iframeName.toString());
		}

			  
		return iframeName.toString();		
	}



	/** 
	 * El método es el punto único y común para la recogida de cualquier excepción generada por los custom tag.
	 * <p>
	 * Una vez recogidas, invoca al <b>Gestor de Excepciones</b> para que las trate.
	 * <p>
	 * 
	 */
	public void doCatch(Throwable arg0) throws Throwable 
	{
		if (tr.isTraceActive())
		{
			tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Throwable de un Custom Tag recogida:"+arg0.getMessage());
			
		}
		Q70GestorDeExcepciones.getSingleton().recogerThrowable(new Q70CustomTagException(this.getClass().toString(),"doCatch","CustomTag ha enviado un throwable al Gestor de Excepciones. Mensaje = ["+arg0.getMessage()+"].",arg0),(HttpServletRequest)(pageContext.getRequest()),(HttpServletResponse)(pageContext.getResponse()));
	}

	
	public void doFinally() {}
	// ------------------------------------------------------------------------ Metodos privados

	/**
	 * Devuelve el modo de display desde el pto de vista de seguridad
	 */
	private int getSecurityDisplayMode() throws JspException
	{	
		int securityDisplayMode = Q70Constants.MODE_READWRITE;  //valor por defecto
		
		if (null != secureId)
		{	

			try  
			{		
				if (tr.isTraceActive())	{
					tr.trace(COMPONENTE, Q70TraceLevel.DEBUG, "Llamando al Gestor de Seguridad con secureId="+secureId+"...");
				} 
			
				//Obtener la informacion de seguridad de usuario del contexto
				Q70ContextoEJIE contextoEJIE = (Q70ContextoEJIE) pageContext.getSession().
												getAttribute(es.ejie.frmk.presentacion.base.Q70Constantes.CONTEXT);
 
				Q70InformacionSeguridadUsuario informacionSeguridadUsuario = 
							(Q70InformacionSeguridadUsuario) contextoEJIE.get(es.ejie.frmk.infraestructura.seguridad.Q70Constantes.SEGURIDAD_USUARIO);
			
				//securityDisplayMode = Q70GestorSeguridad.getSingleton().getCustomTagDisplayMode(informacionSeguridadUsuario, secureId);
				securityDisplayMode = Q70GestorSeguridad.getSingleton().getCustomTagDisplayMode(informacionSeguridadUsuario, (String)pageContext.getServletContext().getInitParameter(Q70GestorArranqueConfiguracion.NOMBRE_APLICACION),secureId);
			} 
			catch(Q70SeguridadException sex) 
			{ 
				if (tr.isTraceActive())	{
					tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "No ha sido posible obtener el modo de display llamando al Gestor de Seguridad para secureId="+secureId);
				} 		
				RequestUtils.saveException(pageContext, sex);
				throw new JspException("No ha sido posible obtener el modo de display llamando al Gestor de Seguridad para secureId="+secureId, sex);
			}
			catch(Exception ex) 
			{
				if (tr.isTraceActive())	{
					tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "No ha sido posible obtener la informacion de seguridad del usuario para secureId="+secureId);
				} 			
				RequestUtils.saveException(pageContext, ex);
				throw new JspException("No ha sido posible obtener la informacion de seguridad del usuario para secureId="+secureId, ex);
			}
			
		}
							
		return securityDisplayMode; 
	}



	/**
	 * El método devuelve una representacion de tipo <code>String</code> del objeto que recibe como argumento.
	 * <p> 
	 * Si el objeto es una instancia de la clase <code>BasicDynaBean</code>, devuelve el valor contenido
	 * en su primer atributo.
	 * <p>
	 * @param (Object) item: objeto a convertir a String
	 * @return (String): valor de tipo <code>String</code> del objeto de entrada
	 */
	private String getString(Object item){
		String ret = "";
		
		if (item instanceof BasicDynaBean)
		{
			//Obtener todas las propiedades del BasicDynaBean 
			DynaProperty[] props = ((BasicDynaBean) item).getDynaClass().getDynaProperties();
			
			//Obtener el valor del primer atributo del BasicDynaBean
			String propName = (props[0]!=null?props[0].getName():"");
			ret = ((BasicDynaBean) item).get(propName).toString();
		}
		else
		{
			ret = item.toString();
		}

		return ret;
	}
}
