/* Copyright 2005-2006 Tim Fennell * * Licensed 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 net.sourceforge.stripes.tag; import net.sourceforge.stripes.action.ActionBean; import net.sourceforge.stripes.controller.StripesConstants; import net.sourceforge.stripes.controller.StripesFilter; import net.sourceforge.stripes.util.Log; import net.sourceforge.stripes.util.ReflectUtil; import javax.servlet.jsp.JspException; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.Tag; import java.util.HashMap; import java.util.ListIterator; import java.util.Map; import java.util.Stack; /** * A very basic implementation of the Tag interface that is similar in manner to the standard * TagSupport class, but with less clutter. * * @author Tim Fennell */ public abstract class StripesTagSupport implements Tag { private static final Log log = Log.getInstance(StripesTagSupport.class); /** Storage for a PageContext during evaluation. */ protected PageContext pageContext; /** Storage for the parent tag of this tag. */ protected Tag parentTag; /** * A map that is used to store values of page context attributes before they were * replaced with other values for the body of the tag. */ private Map<String,Object> previousAttributeValues; /** Called by the Servlet container to set the page context on the tag. */ public void setPageContext(PageContext pageContext) { this.pageContext = pageContext; } /** Retrieves the pageContext handed to the tag by the container. */ public PageContext getPageContext() { return this.pageContext; } /** From the Tag interface - allows the container to set the parent tag on the JSP. */ public void setParent(Tag tag) { this.parentTag = tag; } /** From the Tag interface - allows fetching the parent tag on the JSP. */ public Tag getParent() { return this.parentTag; } /** * Abstract method from the Tag interface. Abstract because it seems to make the * child tags more readable if they implement their own do() methods, even when * they just return one of the constants and do nothing else. */ public abstract int doStartTag() throws JspException; /** * Abstract method from the Tag interface. Abstract because it seems to make the * child tags more readable if they implement their own do() methods, even when * they just return one of the constants and do nothing else. */ public abstract int doEndTag() throws JspException; /** * No-op implementation of release(). */ public void release() { } /** * Pushes new values for the attributes supplied into the page context, preserving * the old values so that they can be put back into page context end of the tag's * execution (usually in doEndTag). If this method is called, the tag <b>must</b> * also call{@link #popPageContextAttributes()}. */ public void pushPageContextAttributes(Map<String,Object> attributes) { this.previousAttributeValues = new HashMap<String,Object>(); for (Map.Entry<String,Object> entry : attributes.entrySet()) { String name = entry.getKey(); this.previousAttributeValues.put(name, pageContext.getAttribute(name)); this.pageContext.setAttribute(name, entry.getValue()); } } /** * Attempts to restore page context attributes to their state prior to a call to * pushPageContextAttributes(). Attributes that had values prior to the execution of * this tag have their values restored. Attributes that did not have values * are removed from the page context. */ public void popPageContextAttributes() { for (Map.Entry<String,Object> entry : this.previousAttributeValues.entrySet()) { if (entry.getValue() == null) { this.pageContext.removeAttribute(entry.getKey()); } else { this.pageContext.setAttribute(entry.getKey(), entry.getValue()); } } // Null out the map so erroneous values don't get picked up on tag pooling! this.previousAttributeValues = null; } /** * <p>Locates the enclosing tag of the type supplied. If no enclosing tag of the type supplied * can be found anywhere in the ancestry of this tag, null is returned.</p> * * @return T Tag of the type supplied, or null if none can be found */ @SuppressWarnings("unchecked") protected <T extends Tag> T getParentTag(Class<T> tagType) { Tag parent = getParent(); while (parent != null) { if (tagType.isAssignableFrom(parent.getClass())) { return (T) parent; } parent = parent.getParent(); } // If we can't find it by the normal way, try our own tag stack! Stack<StripesTagSupport> stack = getTagStack(); ListIterator<StripesTagSupport> iterator = stack.listIterator(stack.size()); while (iterator.hasPrevious() && iterator.previous() != this) continue; while (iterator.hasPrevious()) { StripesTagSupport tag = iterator.previous(); if (tagType.isAssignableFrom(tag.getClass())) { return (T) tag; } } return null; } /** * Fetches a tag stack that is stored in the request. This tag stack is used to help * Stripes tags find one another when they are spread across multiple included JSPs * and/or tag files - situations in which the usual parent tag relationship fails. */ @SuppressWarnings("unchecked") protected Stack<StripesTagSupport> getTagStack() { Stack<StripesTagSupport> stack = (Stack<StripesTagSupport>) getPageContext().getRequest().getAttribute(StripesConstants.REQ_ATTR_TAG_STACK); if (stack == null) { stack = new Stack<StripesTagSupport>(); getPageContext().getRequest().setAttribute(StripesConstants.REQ_ATTR_TAG_STACK, stack); } return stack; } /** * Helper method that takes an attribute which may be either a String class name * or a Class object and returns the Class representing the appropriate ActionBean. * If for any reason the Class cannot be determined, or it is not an ActionBean, null * will be returned instead. * * @param nameOrClass either the String FQN of an ActionBean class, or a Class object * @return the appropriate ActionBean class or null */ @SuppressWarnings("unchecked") protected Class<? extends ActionBean> getActionBeanType(Object nameOrClass) { Class result; // Figure out if it's a String of Class (or something else?) and act appropriately if (nameOrClass instanceof String) { try { result = ReflectUtil.findClass((String) nameOrClass); } catch (ClassNotFoundException cnfe) { log.error(cnfe, "Could not find class of type: ", nameOrClass); return null; } } else if (nameOrClass instanceof Class) { result = (Class) nameOrClass; } else { log.error("The value supplied to getActionBeanType() was neither a String nor a " + "Class. Cannot infer ActionBean type from value: " + nameOrClass); return null; } // And for good measure, let's make sure it's an ActionBean implementation! if (ActionBean.class.isAssignableFrom(result)) { return result; } else { log.error("Class '", result.getName(), "' specified in tag does not implement ", "ActionBean."); return null; } } /** * Similar to the {@link #getActionBeanType(Object)} method except that instead of * returning the Class of ActionBean it returns the URL Binding of the ActionBean. * * @param nameOrClass either the String FQN of an ActionBean class, or a Class object * @return the URL of the appropriate ActionBean class or null */ protected String getActionBeanUrl(Object nameOrClass) { Class<? extends ActionBean> beanType = getActionBeanType(nameOrClass); if (beanType != null) { return StripesFilter.getConfiguration().getActionResolver().getUrlBinding(beanType); } else { return null; } } }