/* * $Id: BaseHandlerTag.java 479633 2006-11-27 14:25:35Z pbenedict $ * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.struts.taglib.html; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.struts.Globals; import org.apache.struts.action.ActionMessages; import org.apache.struts.taglib.TagUtils; import org.apache.struts.taglib.logic.IterateTag; import org.apache.struts.util.MessageResources; import org.apache.struts.util.RequestUtils; import javax.servlet.jsp.JspException; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.BodyTagSupport; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 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. * * @version $Rev: 479633 $ $Date: 2006-11-27 15:25:35 +0100 (Mon, 27 Nov 2006) $ */ public abstract class BaseHandlerTag extends BodyTagSupport { /** * Commons Logging instance. */ private static Log log = LogFactory.getLog(BaseHandlerTag.class); // ----------------------------------------------------- Instance Variables /** * The message resources for this package. */ protected static MessageResources messages = MessageResources.getMessageResources(Constants.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; // 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; /** * Indicates whether 'disabled' is a valid attribute */ protected boolean doDisabled = true; /** * Component is readonly. */ private boolean readonly = false; /** * <p>Indicates whether 'readonly' is a valid attribute.</p> * * <p>According to the HTML 4.0 Specification <readonly> is valid * for <input type="text">, <input type="password"> and * <textarea"> elements. Therefore, except for those tags this value * is set to <code>false</code>.</p> */ protected boolean doReadonly = 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; /** * The request attribute key for our error messages (if any). */ private String errorKey = Globals.ERROR_KEY; /** * Style attribute associated with component when errors exist. */ private String errorStyle = null; /** * Named Style class associated with component when errors exist. */ private String errorStyleClass = null; /** * Identifier associated with component when errors exist. */ private String errorStyleId = 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 message resources bundle for message lookups. */ private String bundle = 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 language code of this element. */ private String lang = null; /** * The direction for weak/neutral text of this element. */ private String dir = null; /** * The message resources key of the advisory title. */ private String titleKey = null; private Class loopTagClass = null; private Method loopTagGetStatus = null; private Class loopTagStatusClass = null; private Method loopTagStatusGetIndex = null; private boolean triedJstlInit = false; private boolean triedJstlSuccess = false; // ------------------------------------------------------------- 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; } /** * Returns the error key attribute. */ public String getErrorKey() { return errorKey; } /** * Sets the error key attribute. */ public void setErrorKey(String errorKey) { this.errorKey = errorKey; } /** * Returns the error style attribute. */ public String getErrorStyle() { return errorStyle; } /** * Sets the error style attribute. */ public void setErrorStyle(String errorStyle) { this.errorStyle = errorStyle; } /** * Returns the error style class attribute. */ public String getErrorStyleClass() { return errorStyleClass; } /** * Sets the error style class attribute. */ public void setErrorStyleClass(String errorStyleClass) { this.errorStyleClass = errorStyleClass; } /** * Returns the error style id attribute. */ public String getErrorStyleId() { return errorStyleId; } /** * Sets the error style id attribute. */ public void setErrorStyleId(String errorStyleId) { this.errorStyleId = errorStyleId; } // 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 message resources bundle to use. */ public String getBundle() { return bundle; } /** * Sets the name of the message resources bundle to use. */ public void setBundle(String bundle) { this.bundle = bundle; } /** * 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; } /** * Returns the language code of this element. * * @since Struts 1.3.6 */ public String getLang() { return this.lang; } /** * Sets the language code of this element. * * @since Struts 1.3.6 */ public void setLang(String lang) { this.lang = lang; } /** * Returns the direction for weak/neutral text this element. * * @since Struts 1.3.6 */ public String getDir() { return this.dir; } /** * Sets the direction for weak/neutral text of this element. * * @since Struts 1.3.6 */ public void setDir(String dir) { this.dir = dir; } // --------------------------------------------------------- Public Methods /** * Release any acquired resources. */ public void release() { super.release(); accesskey = null; alt = null; altKey = null; bundle = null; dir = null; errorKey = Globals.ERROR_KEY; errorStyle = null; errorStyleClass = null; errorStyleId = null; indexed = false; lang = null; 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; } // ------------------------------------------------------ Protected Methods /** * Return the text specified by the literal value or the message resources * key, if any; otherwise return <code>null</code>. * * @param literal Literal text value or <code>null</code> * @param key Message resources key or <code>null</code> * @throws JspException if both arguments are non-null */ protected String message(String literal, String key) throws JspException { if (literal != null) { if (key != null) { JspException e = new JspException(messages.getMessage("common.both")); TagUtils.getInstance().saveException(pageContext, e); throw e; } else { return (literal); } } else { if (key != null) { return TagUtils.getInstance().message(pageContext, getBundle(), getLocale(), key); } else { return null; } } } private Integer getJstlLoopIndex() { if (!triedJstlInit) { triedJstlInit = true; try { loopTagClass = RequestUtils.applicationClass( "javax.servlet.jsp.jstl.core.LoopTag"); loopTagGetStatus = loopTagClass.getDeclaredMethod("getLoopStatus", null); loopTagStatusClass = RequestUtils.applicationClass( "javax.servlet.jsp.jstl.core.LoopTagStatus"); loopTagStatusGetIndex = loopTagStatusClass.getDeclaredMethod("getIndex", null); triedJstlSuccess = true; } catch (ClassNotFoundException ex) { // These just mean that JSTL isn't loaded, so ignore } catch (NoSuchMethodException ex) { } } if (triedJstlSuccess) { try { Object loopTag = findAncestorWithClass(this, loopTagClass); if (loopTag == null) { return null; } Object status = loopTagGetStatus.invoke(loopTag, null); return (Integer) loopTagStatusGetIndex.invoke(status, null); } catch (IllegalAccessException ex) { log.error(ex.getMessage(), ex); } catch (IllegalArgumentException ex) { log.error(ex.getMessage(), ex); } catch (InvocationTargetException ex) { log.error(ex.getMessage(), ex); } catch (NullPointerException ex) { log.error(ex.getMessage(), ex); } catch (ExceptionInInitializerError ex) { log.error(ex.getMessage(), ex); } } 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. * @throws JspException if 'indexed' tag used outside of iterate tag. */ protected void prepareIndex(StringBuffer handlers, String name) throws JspException { if (name != null) { handlers.append(name); } handlers.append("["); handlers.append(getIndexValue()); handlers.append("]"); if (name != null) { handlers.append("."); } } /** * Returns the index value for tags with 'true' value in 'indexed' * attribute. * * @return the index value. * @throws JspException if 'indexed' tag used outside of iterate tag. */ protected int getIndexValue() throws JspException { // look for outer iterate tag IterateTag iterateTag = (IterateTag) findAncestorWithClass(this, IterateTag.class); if (iterateTag != null) { return iterateTag.getIndex(); } // Look for JSTL loops Integer i = getJstlLoopIndex(); if (i != null) { return i.intValue(); } // this tag should be nested in an IterateTag or JSTL loop tag, if it's not, throw exception JspException e = new JspException(messages.getMessage("indexed.noEnclosingIterate")); TagUtils.getInstance().saveException(pageContext, e); throw e; } /** * Prepares the style attributes for inclusion in the component's HTML * tag. * * @return The prepared String for inclusion in the HTML tag. * @throws JspException if invalid attributes are specified */ protected String prepareStyles() throws JspException { StringBuffer styles = new StringBuffer(); boolean errorsExist = doErrorsExist(); if (errorsExist && (getErrorStyleId() != null)) { prepareAttribute(styles, "id", getErrorStyleId()); } else { prepareAttribute(styles, "id", getStyleId()); } if (errorsExist && (getErrorStyle() != null)) { prepareAttribute(styles, "style", getErrorStyle()); } else { prepareAttribute(styles, "style", getStyle()); } if (errorsExist && (getErrorStyleClass() != null)) { prepareAttribute(styles, "class", getErrorStyleClass()); } else { prepareAttribute(styles, "class", getStyleClass()); } prepareAttribute(styles, "title", message(getTitle(), getTitleKey())); prepareAttribute(styles, "alt", message(getAlt(), getAltKey())); prepareInternationalization(styles); return styles.toString(); } /** * Determine if there are errors for the component. * * @return Whether errors exist. */ protected boolean doErrorsExist() throws JspException { boolean errorsExist = false; if ((getErrorStyleId() != null) || (getErrorStyle() != null) || (getErrorStyleClass() != null)) { String actualName = prepareName(); if (actualName != null) { ActionMessages errors = TagUtils.getInstance().getActionMessages(pageContext, errorKey); errorsExist = ((errors != null) && (errors.size(actualName) > 0)); } } return errorsExist; } /** * Prepares the actual name of the component. * * @return The actual component name. */ protected String prepareName() throws JspException { return null; } /** * 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) { prepareAttribute(handlers, "onclick", getOnclick()); prepareAttribute(handlers, "ondblclick", getOndblclick()); prepareAttribute(handlers, "onmouseover", getOnmouseover()); prepareAttribute(handlers, "onmouseout", getOnmouseout()); prepareAttribute(handlers, "onmousemove", getOnmousemove()); prepareAttribute(handlers, "onmousedown", getOnmousedown()); prepareAttribute(handlers, "onmouseup", getOnmouseup()); } /** * 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) { prepareAttribute(handlers, "onkeydown", getOnkeydown()); prepareAttribute(handlers, "onkeyup", getOnkeyup()); prepareAttribute(handlers, "onkeypress", getOnkeypress()); } /** * 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) { prepareAttribute(handlers, "onselect", getOnselect()); prepareAttribute(handlers, "onchange", getOnchange()); } /** * 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) { prepareAttribute(handlers, "onblur", getOnblur()); prepareAttribute(handlers, "onfocus", getOnfocus()); // Get the parent FormTag (if necessary) FormTag formTag = null; if ((doDisabled && !getDisabled()) || (doReadonly && !getReadonly())) { formTag = (FormTag) pageContext.getAttribute(Constants.FORM_KEY, PageContext.REQUEST_SCOPE); } // Format Disabled if (doDisabled) { boolean formDisabled = (formTag == null) ? false : formTag.isDisabled(); if (formDisabled || getDisabled()) { handlers.append(" disabled=\"disabled\""); } } // Format Read Only if (doReadonly) { boolean formReadOnly = (formTag == null) ? false : formTag.isReadonly(); if (formReadOnly || getReadonly()) { handlers.append(" readonly=\"readonly\""); } } } /** * Prepares the internationalization attribtes, appending them to the the given * StringBuffer. * * @param handlers The StringBuffer that output will be appended to. * @since Struts 1.3.6 */ protected void prepareInternationalization(StringBuffer handlers) { prepareAttribute(handlers, "lang", getLang()); prepareAttribute(handlers, "dir", getDir()); } /** * 'Hook' to enable tags to be extended and additional attributes added. * * @param handlers The StringBuffer that output will be appended to. */ protected void prepareOtherAttributes(StringBuffer handlers) { } /** * Prepares an attribute if the value is not null, appending it to the the * given StringBuffer. * * @param handlers The StringBuffer that output will be appended to. */ protected void prepareAttribute(StringBuffer handlers, String name, Object value) { if (value != null) { handlers.append(" "); handlers.append(name); handlers.append("=\""); handlers.append(value); handlers.append("\""); } } /** * Allows HTML tags to find out if they're nested within an * %lt;html:html> 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() { return TagUtils.getInstance().isXhtml(this.pageContext); } /** * Returns the closing brace for an input element depending on xhtml * status. The tag must be nested within an %lt;html:html> tag that * has xhtml set to true. * * @return String - > if xhtml is false, /> if xhtml is true * @since Struts 1.1 */ protected String getElementClose() { return this.isXhtml() ? " />" : ">"; } /** * 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 = TagUtils.getInstance().lookup(this.pageContext, beanName, null); if (bean == null) { throw new JspException(messages.getMessage("getter.bean", beanName)); } try { return BeanUtils.getProperty(bean, property); } catch (IllegalAccessException e) { throw new JspException(messages.getMessage("getter.access", property, beanName)); } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); throw new JspException(messages.getMessage("getter.result", property, t.toString())); } catch (NoSuchMethodException e) { throw new JspException(messages.getMessage("getter.method", property, beanName)); } } }