/* 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 java.net.MalformedURLException; import java.util.HashMap; import java.util.ListIterator; import java.util.Map; import java.util.Stack; 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.Tag; import net.sourceforge.stripes.action.ActionBean; import net.sourceforge.stripes.action.Secure; import net.sourceforge.stripes.controller.StripesConstants; import net.sourceforge.stripes.controller.StripesFilter; import net.sourceforge.stripes.util.HttpUrlInfo; import net.sourceforge.stripes.util.Log; import net.sourceforge.stripes.util.ReflectUtil; import net.sourceforge.stripes.util.UrlParser; /** * 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 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 * must also call{@link #popPageContextAttributes()}. */ public void pushPageContextAttributes(Map attributes) { this.previousAttributeValues = new HashMap(); for (Map.Entry 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 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; } /** *

* 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. *

* * @return T Tag of the type supplied, or null if none can be found */ protected T getParentTag(Class 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 stack = getTagStack(); ListIterator iterator = stack.listIterator(stack.size()); 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. */ protected Stack getTagStack() { Stack stack = (Stack) getPageContext().getRequest().getAttribute( StripesConstants.REQ_ATTR_TAG_STACK); if (stack == null) { stack = new Stack(); 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 */ protected Class getActionBeanType(Object nameOrClass) { Class result = null; // 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 beanType = getActionBeanType(nameOrClass); if (beanType != null) { return StripesFilter.getConfiguration().getActionResolver().getUrlBinding(beanType); } else { return null; } } // BEGIN SSL MOD /** * Utility method used to retrieve a value from the StripesFilter config * * @param key the key to look up * @return the property value of the given key */ protected String getBootstrapProperty(String key) { return StripesFilter.getConfiguration().getBootstrapPropertyResolver().getProperty(key); } /** * Retrieves the {@literal SSL.SessionMode} value from the configuration. If no value is found, {@literal never} is * returned as default value since this is most secure option. * * @return The sessionMode */ protected String getSessionMode() { String sessionMode = getBootstrapProperty("SSL.SessionMode"); if (sessionMode == null || sessionMode.length() == 0) { return "never"; } return sessionMode; } protected String addContextPath(String url, String contextPath) { // Append the context path, but only if the user didn't already if (!"/".equals(contextPath) && !url.contains(contextPath + "/")) { url = contextPath + url; } return url; } /** * Encodes the given URL by adding the session ID in case url rewriting of the session is needed. Since this task is * done by the container, this method will simply delegate to the current {@link HttpServletResponse}. Absolute * URLs (i.e. URLs not starting with a single slash) will not be rewritten. * * @param url the URL that should be encoded * @return the encoded URL including session id if applicable * @see javax.servlet.http.HttpServletResponse */ protected String encodeUrl(String url) { HttpServletRequest request = (HttpServletRequest) getPageContext().getRequest(); HttpServletResponse response = (HttpServletResponse) getPageContext().getResponse(); String contextPath = request.getContextPath(); if (url.startsWith("/")) { // Parse the raw URL HttpUrlInfo urlInfo = null; try { urlInfo = UrlParser.parseUrl(url); } catch (MalformedURLException e) { log.error(e, "Unable to parse Url '", url, "', therefore SSL rewriting cannot be applied."); } // Delegate to the container in order to rewrite the session if necessary url = response.encodeURL(url); String sessionPart = ""; try { if (UrlParser.parseUrl(url).getSession() != null) { sessionPart = ";" + UrlParser.parseUrl(url).getSession(); } } catch (MalformedURLException e) { log.error(e, "Unable to retrieve session information from url '", url, "'."); } if (urlInfo != null) { // Retrieve the destination ActionBean of the url Class clazz = StripesFilter.getConfiguration().getActionResolver() .getActionBeanType(urlInfo.getPath()); if (clazz != null && clazz.isAnnotationPresent(Secure.class) != request.isSecure()) { boolean targetSecure = clazz.isAnnotationPresent(Secure.class); log .debug("Current protection state and destination protection state differ - therefore, SSL rewriting is applied"); String protocol = request.getScheme() + "://"; String host = request.getServerName(); if (request.getServerPort() != HttpUrlInfo.DEFAULT_HTTP_PORT && request.getServerPort() != HttpUrlInfo.DEFAULT_HTTPS_PORT) { host += ":" + request.getServerPort(); } log.debug("default values: ", protocol, host); if (targetSecure && !request.isSecure()) { log.debug("Rewriting url from http to https"); protocol = "https://"; if (getBootstrapProperty("SSL.SecureHost") != null) { host = getBootstrapProperty("SSL.SecureHost"); } } else if (!targetSecure && request.isSecure()) { log.debug("Rewriting url from https to http"); protocol = "http://"; if (getBootstrapProperty("SSL.UnsecureHost") != null) { host = getBootstrapProperty("SSL.UnsecureHost"); } } StringBuffer urlBuffer = new StringBuffer(); urlBuffer.append(protocol); urlBuffer.append(host); // Append the context path, but only if the user didn't already if (!"/".equals(contextPath) && !url.contains(contextPath + "/")) { urlBuffer.append(contextPath); } urlBuffer.append(urlInfo.getPath()); String sessionMode = getSessionMode(); if (sessionMode.equals("auto")) { urlBuffer.append(";").append(sessionPart); } else if (sessionMode.equals("always")) { urlBuffer.append(";jsessionid=").append(request.getSession().getId()); } urlBuffer.append(urlInfo.getCompleteQuery()); urlBuffer.append(urlInfo.getCompleteRef()); url = urlBuffer.toString(); } else { url = addContextPath(url, contextPath); } } else { url = addContextPath(url, contextPath); } } else { // if the url does not start with a / url = response.encodeURL(url); } log.debug("Final url is: ", url); return url; } // END SSL MOD }