0001: /*
0002: * $Id: TagUtils.java 471754 2006-11-06 14:55:09Z husted $
0003: *
0004: * Licensed to the Apache Software Foundation (ASF) under one
0005: * or more contributor license agreements. See the NOTICE file
0006: * distributed with this work for additional information
0007: * regarding copyright ownership. The ASF licenses this file
0008: * to you under the Apache License, Version 2.0 (the
0009: * "License"); you may not use this file except in compliance
0010: * with the License. You may obtain a copy of the License at
0011: *
0012: * http://www.apache.org/licenses/LICENSE-2.0
0013: *
0014: * Unless required by applicable law or agreed to in writing,
0015: * software distributed under the License is distributed on an
0016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
0017: * KIND, either express or implied. See the License for the
0018: * specific language governing permissions and limitations
0019: * under the License.
0020: */
0021: package org.apache.struts.taglib;
0022:
0023: import org.apache.commons.beanutils.PropertyUtils;
0024: import org.apache.commons.logging.Log;
0025: import org.apache.commons.logging.LogFactory;
0026: import org.apache.struts.Globals;
0027: import org.apache.struts.action.ActionErrors;
0028: import org.apache.struts.action.ActionMessage;
0029: import org.apache.struts.action.ActionMessages;
0030: import org.apache.struts.action.ActionServlet;
0031: import org.apache.struts.config.ForwardConfig;
0032: import org.apache.struts.config.ModuleConfig;
0033: import org.apache.struts.taglib.html.Constants;
0034: import org.apache.struts.util.MessageResources;
0035: import org.apache.struts.util.ModuleUtils;
0036: import org.apache.struts.util.RequestUtils;
0037: import org.apache.struts.util.ResponseUtils;
0038:
0039: import javax.servlet.http.HttpServletRequest;
0040: import javax.servlet.http.HttpServletResponse;
0041: import javax.servlet.http.HttpSession;
0042: import javax.servlet.jsp.JspException;
0043: import javax.servlet.jsp.JspWriter;
0044: import javax.servlet.jsp.PageContext;
0045: import javax.servlet.jsp.tagext.BodyContent;
0046:
0047: import java.io.IOException;
0048:
0049: import java.lang.reflect.InvocationTargetException;
0050:
0051: import java.net.MalformedURLException;
0052:
0053: import java.util.HashMap;
0054: import java.util.Iterator;
0055: import java.util.Locale;
0056: import java.util.Map;
0057:
0058: /**
0059: * Provides helper methods for JSP tags.
0060: *
0061: * @version $Rev: 471754 $
0062: * @since Struts 1.2
0063: */
0064: public class TagUtils {
0065: /**
0066: * The Singleton instance.
0067: * @since 1.3.5 Changed to non-final so it may be overridden, use at your own risk (you've been warned!!)
0068: */
0069: private static TagUtils instance = new TagUtils();
0070:
0071: /**
0072: * Commons logging instance.
0073: */
0074: private static final Log log = LogFactory.getLog(TagUtils.class);
0075:
0076: /**
0077: * The message resources for this package. TODO We need to move the
0078: * relevant messages out of this properties file.
0079: */
0080: private static final MessageResources messages = MessageResources
0081: .getMessageResources("org.apache.struts.taglib.LocalStrings");
0082:
0083: /**
0084: * Maps lowercase JSP scope names to their PageContext integer constant
0085: * values.
0086: */
0087: private static final Map scopes = new HashMap();
0088:
0089: /**
0090: * Initialize the scope names map and the encode variable with the
0091: * Java 1.4 method if available.
0092: */
0093: static {
0094: scopes.put("page", new Integer(PageContext.PAGE_SCOPE));
0095: scopes.put("request", new Integer(PageContext.REQUEST_SCOPE));
0096: scopes.put("session", new Integer(PageContext.SESSION_SCOPE));
0097: scopes.put("application", new Integer(
0098: PageContext.APPLICATION_SCOPE));
0099: }
0100:
0101: /**
0102: * Constructor for TagUtils.
0103: */
0104: protected TagUtils() {
0105: super ();
0106: }
0107:
0108: /**
0109: * Returns the Singleton instance of TagUtils.
0110: */
0111: public static TagUtils getInstance() {
0112: return instance;
0113: }
0114:
0115: /**
0116: * Set the instance.
0117: * This blatently violates the Singleton pattern, but then some say Singletons are an anti-pattern.
0118: * @since 1.3.5 Changed to non-final and added setInstance() so TagUtils may be overridden, use at your own risk (you've been warned!!)
0119: * @param instance The instance to set.
0120: */
0121: public static void setInstance(TagUtils instance) {
0122: TagUtils.instance = instance;
0123: }
0124:
0125: /**
0126: * Compute a set of query parameters that will be dynamically added to a
0127: * generated URL. The returned Map is keyed by parameter name, and the
0128: * values are either null (no value specified), a String (single value
0129: * specified), or a String[] array (multiple values specified). Parameter
0130: * names correspond to the corresponding attributes of the
0131: * <code><html:link></code> tag. If no query parameters are
0132: * identified, return <code>null</code>.
0133: *
0134: * @param pageContext PageContext we are operating in
0135: * @param paramId Single-value request parameter name (if any)
0136: * @param paramName Bean containing single-value parameter value
0137: * @param paramProperty Property (of bean named by <code>paramName</code>
0138: * containing single-value parameter value
0139: * @param paramScope Scope containing bean named by <code>paramName</code>
0140: * @param name Bean containing multi-value parameters Map (if
0141: * any)
0142: * @param property Property (of bean named by <code>name</code>
0143: * containing multi-value parameters Map
0144: * @param scope Scope containing bean named by <code>name</code>
0145: * @param transaction Should we add our transaction control token?
0146: * @return Map of query parameters
0147: * @throws JspException if we cannot look up the required beans
0148: * @throws JspException if a class cast exception occurs on a looked-up
0149: * bean or property
0150: */
0151: public Map computeParameters(PageContext pageContext,
0152: String paramId, String paramName, String paramProperty,
0153: String paramScope, String name, String property,
0154: String scope, boolean transaction) throws JspException {
0155: // Short circuit if no parameters are specified
0156: if ((paramId == null) && (name == null) && !transaction) {
0157: return (null);
0158: }
0159:
0160: // Locate the Map containing our multi-value parameters map
0161: Map map = null;
0162:
0163: try {
0164: if (name != null) {
0165: map = (Map) getInstance().lookup(pageContext, name,
0166: property, scope);
0167: }
0168:
0169: // @TODO - remove this - it is never thrown
0170: // } catch (ClassCastException e) {
0171: // saveException(pageContext, e);
0172: // throw new JspException(
0173: // messages.getMessage("parameters.multi", name, property, scope));
0174: } catch (JspException e) {
0175: saveException(pageContext, e);
0176: throw e;
0177: }
0178:
0179: // Create a Map to contain our results from the multi-value parameters
0180: Map results = null;
0181:
0182: if (map != null) {
0183: results = new HashMap(map);
0184: } else {
0185: results = new HashMap();
0186: }
0187:
0188: // Add the single-value parameter (if any)
0189: if ((paramId != null) && (paramName != null)) {
0190: Object paramValue = null;
0191:
0192: try {
0193: paramValue = TagUtils.getInstance().lookup(pageContext,
0194: paramName, paramProperty, paramScope);
0195: } catch (JspException e) {
0196: saveException(pageContext, e);
0197: throw e;
0198: }
0199:
0200: if (paramValue != null) {
0201: String paramString = null;
0202:
0203: if (paramValue instanceof String) {
0204: paramString = (String) paramValue;
0205: } else {
0206: paramString = paramValue.toString();
0207: }
0208:
0209: Object mapValue = results.get(paramId);
0210:
0211: if (mapValue == null) {
0212: results.put(paramId, paramString);
0213: } else if (mapValue instanceof String[]) {
0214: String[] oldValues = (String[]) mapValue;
0215: String[] newValues = new String[oldValues.length + 1];
0216:
0217: System.arraycopy(oldValues, 0, newValues, 0,
0218: oldValues.length);
0219: newValues[oldValues.length] = paramString;
0220: results.put(paramId, newValues);
0221: } else {
0222: String[] newValues = new String[2];
0223:
0224: newValues[0] = mapValue.toString();
0225: newValues[1] = paramString;
0226: results.put(paramId, newValues);
0227: }
0228: }
0229: }
0230:
0231: // Add our transaction control token (if requested)
0232: if (transaction) {
0233: HttpSession session = pageContext.getSession();
0234: String token = null;
0235:
0236: if (session != null) {
0237: token = (String) session
0238: .getAttribute(Globals.TRANSACTION_TOKEN_KEY);
0239: }
0240:
0241: if (token != null) {
0242: results.put(Constants.TOKEN_KEY, token);
0243: }
0244: }
0245:
0246: // Return the completed Map
0247: return (results);
0248: }
0249:
0250: public String computeURL(PageContext pageContext, String forward,
0251: String href, String page, String action, String module,
0252: Map params, String anchor, boolean redirect)
0253: throws MalformedURLException {
0254: return this .computeURLWithCharEncoding(pageContext, forward,
0255: href, page, action, module, params, anchor, redirect,
0256: false);
0257: }
0258:
0259: /**
0260: * Compute a hyperlink URL based on the <code>forward</code>,
0261: * <code>href</code>, <code>action</code> or <code>page</code> parameter
0262: * that is not null. The returned URL will have already been passed to
0263: * <code>response.encodeURL()</code> for adding a session identifier.
0264: *
0265: * @param pageContext PageContext for the tag making this call
0266: * @param forward Logical forward name for which to look up the
0267: * context-relative URI (if specified)
0268: * @param href URL to be utilized unmodified (if specified)
0269: * @param page Module-relative page for which a URL should be
0270: * created (if specified)
0271: * @param action Logical action name for which to look up the
0272: * context-relative URI (if specified)
0273: * @param params Map of parameters to be dynamically included (if
0274: * any)
0275: * @param anchor Anchor to be dynamically included (if any)
0276: * @param redirect Is this URL for a <code>response.sendRedirect()</code>?
0277: * @return URL with session identifier
0278: * @throws java.net.MalformedURLException if a URL cannot be created for
0279: * the specified parameters
0280: */
0281: public String computeURLWithCharEncoding(PageContext pageContext,
0282: String forward, String href, String page, String action,
0283: String module, Map params, String anchor, boolean redirect,
0284: boolean useLocalEncoding) throws MalformedURLException {
0285: return computeURLWithCharEncoding(pageContext, forward, href,
0286: page, action, module, params, anchor, redirect, true,
0287: useLocalEncoding);
0288: }
0289:
0290: public String computeURL(PageContext pageContext, String forward,
0291: String href, String page, String action, String module,
0292: Map params, String anchor, boolean redirect,
0293: boolean encodeSeparator) throws MalformedURLException {
0294: return computeURLWithCharEncoding(pageContext, forward, href,
0295: page, action, module, params, anchor, redirect,
0296: encodeSeparator, false);
0297: }
0298:
0299: /**
0300: * Compute a hyperlink URL based on the <code>forward</code>,
0301: * <code>href</code>, <code>action</code> or <code>page</code> parameter
0302: * that is not null. The returned URL will have already been passed to
0303: * <code>response.encodeURL()</code> for adding a session identifier.
0304: *
0305: * @param pageContext PageContext for the tag making this call
0306: * @param forward Logical forward name for which to look up the
0307: * context-relative URI (if specified)
0308: * @param href URL to be utilized unmodified (if specified)
0309: * @param page Module-relative page for which a URL should be
0310: * created (if specified)
0311: * @param action Logical action name for which to look up the
0312: * context-relative URI (if specified)
0313: * @param params Map of parameters to be dynamically included
0314: * (if any)
0315: * @param anchor Anchor to be dynamically included (if any)
0316: * @param redirect Is this URL for a <code>response.sendRedirect()</code>?
0317: * @param encodeSeparator This is only checked if redirect is set to
0318: * false (never encoded for a redirect). If true,
0319: * query string parameter separators are encoded
0320: * as >amp;, else & is used.
0321: * @param useLocalEncoding If set to true, urlencoding is done on the
0322: * bytes of character encoding from
0323: * ServletResponse#getCharacterEncoding. Use UTF-8
0324: * otherwise.
0325: * @return URL with session identifier
0326: * @throws java.net.MalformedURLException if a URL cannot be created for
0327: * the specified parameters
0328: */
0329: public String computeURLWithCharEncoding(PageContext pageContext,
0330: String forward, String href, String page, String action,
0331: String module, Map params, String anchor, boolean redirect,
0332: boolean encodeSeparator, boolean useLocalEncoding)
0333: throws MalformedURLException {
0334: String charEncoding = "UTF-8";
0335:
0336: if (useLocalEncoding) {
0337: charEncoding = pageContext.getResponse()
0338: .getCharacterEncoding();
0339: }
0340:
0341: // TODO All the computeURL() methods need refactoring!
0342: // Validate that exactly one specifier was included
0343: int n = 0;
0344:
0345: if (forward != null) {
0346: n++;
0347: }
0348:
0349: if (href != null) {
0350: n++;
0351: }
0352:
0353: if (page != null) {
0354: n++;
0355: }
0356:
0357: if (action != null) {
0358: n++;
0359: }
0360:
0361: if (n != 1) {
0362: throw new MalformedURLException(messages
0363: .getMessage("computeURL.specifier"));
0364: }
0365:
0366: // Look up the module configuration for this request
0367: ModuleConfig moduleConfig = getModuleConfig(module, pageContext);
0368:
0369: // Calculate the appropriate URL
0370: StringBuffer url = new StringBuffer();
0371: HttpServletRequest request = (HttpServletRequest) pageContext
0372: .getRequest();
0373:
0374: if (forward != null) {
0375: ForwardConfig forwardConfig = moduleConfig
0376: .findForwardConfig(forward);
0377:
0378: if (forwardConfig == null) {
0379: throw new MalformedURLException(messages.getMessage(
0380: "computeURL.forward", forward));
0381: }
0382:
0383: // **** removed - see bug 37817 ****
0384: // if (forwardConfig.getRedirect()) {
0385: // redirect = true;
0386: // }
0387:
0388: if (forwardConfig.getPath().startsWith("/")) {
0389: url.append(request.getContextPath());
0390: url.append(RequestUtils.forwardURL(request,
0391: forwardConfig, moduleConfig));
0392: } else {
0393: url.append(forwardConfig.getPath());
0394: }
0395: } else if (href != null) {
0396: url.append(href);
0397: } else if (action != null) {
0398: ActionServlet servlet = (ActionServlet) pageContext
0399: .getServletContext().getAttribute(
0400: Globals.ACTION_SERVLET_KEY);
0401: String actionIdPath = RequestUtils.actionIdURL(action,
0402: moduleConfig, servlet);
0403: if (actionIdPath != null) {
0404: action = actionIdPath;
0405: url.append(request.getContextPath());
0406: url.append(actionIdPath);
0407: } else {
0408: url.append(instance.getActionMappingURL(action, module,
0409: pageContext, false));
0410: }
0411: } else /* if (page != null) */
0412: {
0413: url.append(request.getContextPath());
0414: url.append(this .pageURL(request, page, moduleConfig));
0415: }
0416:
0417: // Add anchor if requested (replacing any existing anchor)
0418: if (anchor != null) {
0419: String temp = url.toString();
0420: int hash = temp.indexOf('#');
0421:
0422: if (hash >= 0) {
0423: url.setLength(hash);
0424: }
0425:
0426: url.append('#');
0427: url.append(this .encodeURL(anchor, charEncoding));
0428: }
0429:
0430: // Add dynamic parameters if requested
0431: if ((params != null) && (params.size() > 0)) {
0432: // Save any existing anchor
0433: String temp = url.toString();
0434: int hash = temp.indexOf('#');
0435:
0436: if (hash >= 0) {
0437: anchor = temp.substring(hash + 1);
0438: url.setLength(hash);
0439: temp = url.toString();
0440: } else {
0441: anchor = null;
0442: }
0443:
0444: // Define the parameter separator
0445: String separator = null;
0446:
0447: if (redirect) {
0448: separator = "&";
0449: } else if (encodeSeparator) {
0450: separator = "&";
0451: } else {
0452: separator = "&";
0453: }
0454:
0455: // Add the required request parameters
0456: boolean question = temp.indexOf('?') >= 0;
0457: Iterator keys = params.keySet().iterator();
0458:
0459: while (keys.hasNext()) {
0460: String key = (String) keys.next();
0461: Object value = params.get(key);
0462:
0463: if (value == null) {
0464: if (!question) {
0465: url.append('?');
0466: question = true;
0467: } else {
0468: url.append(separator);
0469: }
0470:
0471: url.append(this .encodeURL(key, charEncoding));
0472: url.append('='); // Interpret null as "no value"
0473: } else if (value instanceof String) {
0474: if (!question) {
0475: url.append('?');
0476: question = true;
0477: } else {
0478: url.append(separator);
0479: }
0480:
0481: url.append(this .encodeURL(key, charEncoding));
0482: url.append('=');
0483: url.append(this .encodeURL((String) value,
0484: charEncoding));
0485: } else if (value instanceof String[]) {
0486: String[] values = (String[]) value;
0487:
0488: for (int i = 0; i < values.length; i++) {
0489: if (!question) {
0490: url.append('?');
0491: question = true;
0492: } else {
0493: url.append(separator);
0494: }
0495:
0496: url.append(this .encodeURL(key, charEncoding));
0497: url.append('=');
0498: url.append(this .encodeURL(values[i],
0499: charEncoding));
0500: }
0501: } else /* Convert other objects to a string */
0502: {
0503: if (!question) {
0504: url.append('?');
0505: question = true;
0506: } else {
0507: url.append(separator);
0508: }
0509:
0510: url.append(this .encodeURL(key, charEncoding));
0511: url.append('=');
0512: url.append(this .encodeURL(value.toString(),
0513: charEncoding));
0514: }
0515: }
0516:
0517: // Re-add the saved anchor (if any)
0518: if (anchor != null) {
0519: url.append('#');
0520: url.append(this .encodeURL(anchor, charEncoding));
0521: }
0522: }
0523:
0524: // Perform URL rewriting to include our session ID (if any)
0525: // but only if url is not an external URL
0526: if ((href == null) && (pageContext.getSession() != null)) {
0527: HttpServletResponse response = (HttpServletResponse) pageContext
0528: .getResponse();
0529:
0530: if (redirect) {
0531: return (response.encodeRedirectURL(url.toString()));
0532: }
0533:
0534: return (response.encodeURL(url.toString()));
0535: }
0536:
0537: return (url.toString());
0538: }
0539:
0540: /**
0541: * URLencodes a string assuming the character encoding is UTF-8.
0542: *
0543: * @param url
0544: * @return String The encoded url in UTF-8
0545: */
0546: public String encodeURL(String url) {
0547: return encodeURL(url, "UTF-8");
0548: }
0549:
0550: /**
0551: * Use the new URLEncoder.encode() method from Java 1.4 if available, else
0552: * use the old deprecated version. This method uses reflection to find
0553: * the appropriate method; if the reflection operations throw exceptions,
0554: * this will return the url encoded with the old URLEncoder.encode()
0555: * method.
0556: *
0557: * @param enc The character encoding the urlencode is performed on.
0558: * @return String The encoded url.
0559: */
0560: public String encodeURL(String url, String enc) {
0561: return ResponseUtils.encodeURL(url, enc);
0562: }
0563:
0564: /**
0565: * Filter the specified string for characters that are senstive to HTML
0566: * interpreters, returning the string with these characters replaced by
0567: * the corresponding character entities.
0568: *
0569: * @param value The string to be filtered and returned
0570: */
0571: public String filter(String value) {
0572: return ResponseUtils.filter(value);
0573: }
0574:
0575: /**
0576: * Return the form action converted into an action mapping path. The
0577: * value of the <code>action</code> property is manipulated as follows in
0578: * computing the name of the requested mapping:
0579: *
0580: * <ul>
0581: *
0582: * <li>Any filename extension is removed (on the theory that extension
0583: * mapping is being used to select the controller servlet).</li>
0584: *
0585: * <li>If the resulting value does not start with a slash, then a slash is
0586: * prepended.</li>
0587: *
0588: * </ul>
0589: */
0590: public String getActionMappingName(String action) {
0591: String value = action;
0592: int question = action.indexOf("?");
0593:
0594: if (question >= 0) {
0595: value = value.substring(0, question);
0596: }
0597:
0598: int pound = value.indexOf("#");
0599:
0600: if (pound >= 0) {
0601: value = value.substring(0, pound);
0602: }
0603:
0604: int slash = value.lastIndexOf("/");
0605: int period = value.lastIndexOf(".");
0606:
0607: if ((period >= 0) && (period > slash)) {
0608: value = value.substring(0, period);
0609: }
0610:
0611: return value.startsWith("/") ? value : ("/" + value);
0612: }
0613:
0614: /**
0615: * Return the form action converted into a server-relative URL.
0616: */
0617: public String getActionMappingURL(String action,
0618: PageContext pageContext) {
0619: return getActionMappingURL(action, null, pageContext, false);
0620: }
0621:
0622: /**
0623: * Return the form action converted into a server-relative URL.
0624: */
0625: public String getActionMappingURL(String action, String module,
0626: PageContext pageContext, boolean contextRelative) {
0627: HttpServletRequest request = (HttpServletRequest) pageContext
0628: .getRequest();
0629:
0630: String contextPath = request.getContextPath();
0631: StringBuffer value = new StringBuffer();
0632:
0633: // Avoid setting two slashes at the beginning of an action:
0634: // the length of contextPath should be more than 1
0635: // in case of non-root context, otherwise length==1 (the slash)
0636: if (contextPath.length() > 1) {
0637: value.append(contextPath);
0638: }
0639:
0640: ModuleConfig moduleConfig = getModuleConfig(module, pageContext);
0641:
0642: if ((moduleConfig != null) && (!contextRelative)) {
0643: value.append(moduleConfig.getPrefix());
0644: }
0645:
0646: // Use our servlet mapping, if one is specified
0647: String servletMapping = (String) pageContext.getAttribute(
0648: Globals.SERVLET_KEY, PageContext.APPLICATION_SCOPE);
0649:
0650: if (servletMapping != null) {
0651: String queryString = null;
0652: int question = action.indexOf("?");
0653:
0654: if (question >= 0) {
0655: queryString = action.substring(question);
0656: }
0657:
0658: String actionMapping = getActionMappingName(action);
0659:
0660: if (servletMapping.startsWith("*.")) {
0661: value.append(actionMapping);
0662: value.append(servletMapping.substring(1));
0663: } else if (servletMapping.endsWith("/*")) {
0664: value.append(servletMapping.substring(0, servletMapping
0665: .length() - 2));
0666: value.append(actionMapping);
0667: } else if (servletMapping.equals("/")) {
0668: value.append(actionMapping);
0669: }
0670:
0671: if (queryString != null) {
0672: value.append(queryString);
0673: }
0674: }
0675: // Otherwise, assume extension mapping is in use and extension is
0676: // already included in the action property
0677: else {
0678: if (!action.startsWith("/")) {
0679: value.append("/");
0680: }
0681:
0682: value.append(action);
0683: }
0684:
0685: return value.toString();
0686: }
0687:
0688: /**
0689: * Retrieves the value from request scope and if it isn't already an
0690: * <code>ActionMessages</code>, some classes are converted to one.
0691: *
0692: * @param pageContext The PageContext for the current page
0693: * @param paramName Key for parameter value
0694: * @return ActionErrors in page context.
0695: * @throws JspException
0696: */
0697: public ActionMessages getActionMessages(PageContext pageContext,
0698: String paramName) throws JspException {
0699: ActionMessages am = new ActionMessages();
0700:
0701: Object value = pageContext.findAttribute(paramName);
0702:
0703: if (value != null) {
0704: try {
0705: if (value instanceof String) {
0706: am.add(ActionMessages.GLOBAL_MESSAGE,
0707: new ActionMessage((String) value));
0708: } else if (value instanceof String[]) {
0709: String[] keys = (String[]) value;
0710:
0711: for (int i = 0; i < keys.length; i++) {
0712: am.add(ActionMessages.GLOBAL_MESSAGE,
0713: new ActionMessage(keys[i]));
0714: }
0715: } else if (value instanceof ActionErrors) {
0716: ActionMessages m = (ActionMessages) value;
0717:
0718: am.add(m);
0719: } else if (value instanceof ActionMessages) {
0720: am = (ActionMessages) value;
0721: } else {
0722: throw new JspException(messages.getMessage(
0723: "actionMessages.errors", value.getClass()
0724: .getName()));
0725: }
0726: } catch (JspException e) {
0727: throw e;
0728: } catch (Exception e) {
0729: log.warn(
0730: "Unable to retieve ActionMessage for paramName : "
0731: + paramName, e);
0732: }
0733: }
0734:
0735: return am;
0736: }
0737:
0738: /**
0739: * Return the default ModuleConfig object if it exists, null if
0740: * otherwise.
0741: *
0742: * @param pageContext The page context.
0743: * @return the ModuleConfig object
0744: */
0745: public ModuleConfig getModuleConfig(PageContext pageContext) {
0746: return getModuleConfig(null, pageContext);
0747: }
0748:
0749: /**
0750: * Return the specified ModuleConfig object for the given prefix if it
0751: * exists, otherwise a NullPointerException will be thrown.
0752: *
0753: * @param module The module prefix
0754: * @param pageContext The page context.
0755: * @return the ModuleConfig object
0756: * @throws NullPointerException Thrown when module cannot be found
0757: */
0758: public ModuleConfig getModuleConfig(String module,
0759: PageContext pageContext) {
0760: ModuleConfig config = ModuleUtils.getInstance()
0761: .getModuleConfig(module,
0762: (HttpServletRequest) pageContext.getRequest(),
0763: pageContext.getServletContext());
0764:
0765: // ModuleConfig not found
0766: if (config == null) {
0767: throw new NullPointerException("Module '" + module
0768: + "' not found.");
0769: }
0770:
0771: return config;
0772: }
0773:
0774: /**
0775: * Converts the scope name into its corresponding PageContext constant
0776: * value.
0777: *
0778: * @param scopeName Can be "page", "request", "session", or "application"
0779: * in any case.
0780: * @return The constant representing the scope (ie. PageContext.REQUEST_SCOPE).
0781: * @throws JspException if the scopeName is not a valid name.
0782: */
0783: public int getScope(String scopeName) throws JspException {
0784: Integer scope = (Integer) scopes.get(scopeName.toLowerCase());
0785:
0786: if (scope == null) {
0787: throw new JspException(messages.getMessage("lookup.scope",
0788: scope));
0789: }
0790:
0791: return scope.intValue();
0792: }
0793:
0794: /**
0795: * Look up and return current user locale, based on the specified
0796: * parameters.
0797: *
0798: * @param pageContext The PageContext associated with this request
0799: * @param locale Name of the session attribute for our user's Locale.
0800: * If this is <code>null</code>, the default locale key
0801: * is used for the lookup.
0802: * @return current user locale
0803: */
0804: public Locale getUserLocale(PageContext pageContext, String locale) {
0805: return RequestUtils.getUserLocale(
0806: (HttpServletRequest) pageContext.getRequest(), locale);
0807: }
0808:
0809: /**
0810: * Returns true if the custom tags are in XHTML mode.
0811: */
0812: public boolean isXhtml(PageContext pageContext) {
0813: String xhtml = (String) pageContext.getAttribute(
0814: Globals.XHTML_KEY, PageContext.PAGE_SCOPE);
0815:
0816: return "true".equalsIgnoreCase(xhtml);
0817: }
0818:
0819: /**
0820: * Locate and return the specified bean, from an optionally specified
0821: * scope, in the specified page context. If no such bean is found, return
0822: * <code>null</code> instead. If an exception is thrown, it will have
0823: * already been saved via a call to <code>saveException()</code>.
0824: *
0825: * @param pageContext Page context to be searched
0826: * @param name Name of the bean to be retrieved
0827: * @param scopeName Scope to be searched (page, request, session,
0828: * application) or <code>null</code> to use
0829: * <code>findAttribute()</code> instead
0830: * @return JavaBean in the specified page context
0831: * @throws JspException if an invalid scope name is requested
0832: */
0833: public Object lookup(PageContext pageContext, String name,
0834: String scopeName) throws JspException {
0835: if (scopeName == null) {
0836: return pageContext.findAttribute(name);
0837: }
0838:
0839: try {
0840: return pageContext.getAttribute(name, instance
0841: .getScope(scopeName));
0842: } catch (JspException e) {
0843: saveException(pageContext, e);
0844: throw e;
0845: }
0846: }
0847:
0848: /**
0849: * Locate and return the specified property of the specified bean, from an
0850: * optionally specified scope, in the specified page context. If an
0851: * exception is thrown, it will have already been saved via a call to
0852: * <code>saveException()</code>.
0853: *
0854: * @param pageContext Page context to be searched
0855: * @param name Name of the bean to be retrieved
0856: * @param property Name of the property to be retrieved, or
0857: * <code>null</code> to retrieve the bean itself
0858: * @param scope Scope to be searched (page, request, session,
0859: * application) or <code>null</code> to use
0860: * <code>findAttribute()</code> instead
0861: * @return property of specified JavaBean
0862: * @throws JspException if an invalid scope name is requested
0863: * @throws JspException if the specified bean is not found
0864: * @throws JspException if accessing this property causes an
0865: * IllegalAccessException, IllegalArgumentException,
0866: * InvocationTargetException, or NoSuchMethodException
0867: */
0868: public Object lookup(PageContext pageContext, String name,
0869: String property, String scope) throws JspException {
0870: // Look up the requested bean, and return if requested
0871: Object bean = lookup(pageContext, name, scope);
0872:
0873: if (bean == null) {
0874: JspException e = null;
0875:
0876: if (scope == null) {
0877: e = new JspException(messages.getMessage(
0878: "lookup.bean.any", name));
0879: } else {
0880: e = new JspException(messages.getMessage("lookup.bean",
0881: name, scope));
0882: }
0883:
0884: saveException(pageContext, e);
0885: throw e;
0886: }
0887:
0888: if (property == null) {
0889: return bean;
0890: }
0891:
0892: // Locate and return the specified property
0893: try {
0894: return PropertyUtils.getProperty(bean, property);
0895: } catch (IllegalAccessException e) {
0896: saveException(pageContext, e);
0897: throw new JspException(messages.getMessage("lookup.access",
0898: property, name));
0899: } catch (IllegalArgumentException e) {
0900: saveException(pageContext, e);
0901: throw new JspException(messages.getMessage(
0902: "lookup.argument", property, name));
0903: } catch (InvocationTargetException e) {
0904: Throwable t = e.getTargetException();
0905:
0906: if (t == null) {
0907: t = e;
0908: }
0909:
0910: saveException(pageContext, t);
0911: throw new JspException(messages.getMessage("lookup.target",
0912: property, name));
0913: } catch (NoSuchMethodException e) {
0914: saveException(pageContext, e);
0915:
0916: String beanName = name;
0917:
0918: // Name defaults to Contants.BEAN_KEY if no name is specified by
0919: // an input tag. Thus lookup the bean under the key and use
0920: // its class name for the exception message.
0921: if (Constants.BEAN_KEY.equals(name)) {
0922: Object obj = pageContext
0923: .findAttribute(Constants.BEAN_KEY);
0924:
0925: if (obj != null) {
0926: beanName = obj.getClass().getName();
0927: }
0928: }
0929:
0930: throw new JspException(messages.getMessage("lookup.method",
0931: property, beanName));
0932: }
0933: }
0934:
0935: /**
0936: * Look up and return a message string, based on the specified
0937: * parameters.
0938: *
0939: * @param pageContext The PageContext associated with this request
0940: * @param bundle Name of the servlet context attribute for our
0941: * message resources bundle
0942: * @param locale Name of the session attribute for our user's Locale
0943: * @param key Message key to be looked up and returned
0944: * @return message string
0945: * @throws JspException if a lookup error occurs (will have been saved in
0946: * the request already)
0947: */
0948: public String message(PageContext pageContext, String bundle,
0949: String locale, String key) throws JspException {
0950: return message(pageContext, bundle, locale, key, null);
0951: }
0952:
0953: /**
0954: * Look up and return a message string, based on the specified
0955: * parameters.
0956: *
0957: * @param pageContext The PageContext associated with this request
0958: * @param bundle Name of the servlet context attribute for our
0959: * message resources bundle
0960: * @param locale Name of the session attribute for our user's Locale
0961: * @param key Message key to be looked up and returned
0962: * @param args Replacement parameters for this message
0963: * @return message string
0964: * @throws JspException if a lookup error occurs (will have been saved in
0965: * the request already)
0966: */
0967: public String message(PageContext pageContext, String bundle,
0968: String locale, String key, Object[] args)
0969: throws JspException {
0970: MessageResources resources = retrieveMessageResources(
0971: pageContext, bundle, false);
0972:
0973: Locale userLocale = getUserLocale(pageContext, locale);
0974: String message = null;
0975:
0976: if (args == null) {
0977: message = resources.getMessage(userLocale, key);
0978: } else {
0979: message = resources.getMessage(userLocale, key, args);
0980: }
0981:
0982: if ((message == null) && log.isDebugEnabled()) {
0983: // log missing key to ease debugging
0984: log.debug(resources.getMessage("message.resources", key,
0985: bundle, locale));
0986: }
0987:
0988: return message;
0989: }
0990:
0991: /**
0992: * <p>Return the context-relative URL that corresponds to the specified
0993: * <code>page</code> attribute value, calculated based on the
0994: * <code>pagePattern</code> property of the current module's {@link
0995: * ModuleConfig}.</p>
0996: *
0997: * @param request The servlet request we are processing
0998: * @param page The module-relative URL to be substituted in to the
0999: * <code>pagePattern</code> pattern for the current module
1000: * (<strong>MUST</strong> start with a slash)
1001: * @return context-relative URL
1002: */
1003: public String pageURL(HttpServletRequest request, String page,
1004: ModuleConfig moduleConfig) {
1005: StringBuffer sb = new StringBuffer();
1006: String pagePattern = moduleConfig.getControllerConfig()
1007: .getPagePattern();
1008:
1009: if (pagePattern == null) {
1010: sb.append(moduleConfig.getPrefix());
1011: sb.append(page);
1012: } else {
1013: boolean dollar = false;
1014:
1015: for (int i = 0; i < pagePattern.length(); i++) {
1016: char ch = pagePattern.charAt(i);
1017:
1018: if (dollar) {
1019: switch (ch) {
1020: case 'M':
1021: sb.append(moduleConfig.getPrefix());
1022:
1023: break;
1024:
1025: case 'P':
1026: sb.append(page);
1027:
1028: break;
1029:
1030: case '$':
1031: sb.append('$');
1032:
1033: break;
1034:
1035: default:
1036: ; // Silently swallow
1037: }
1038:
1039: dollar = false;
1040:
1041: continue;
1042: } else if (ch == '$') {
1043: dollar = true;
1044: } else {
1045: sb.append(ch);
1046: }
1047: }
1048: }
1049:
1050: return sb.toString();
1051: }
1052:
1053: /**
1054: * Return true if a message string for the specified message key is
1055: * present for the specified <code>Locale</code> and bundle.
1056: *
1057: * @param pageContext The PageContext associated with this request
1058: * @param bundle Name of the servlet context attribute for our
1059: * message resources bundle
1060: * @param locale Name of the session attribute for our user's Locale
1061: * @param key Message key to be looked up and returned
1062: * @return true if a message string for message key exists
1063: * @throws JspException if a lookup error occurs (will have been saved in
1064: * the request already)
1065: */
1066: public boolean present(PageContext pageContext, String bundle,
1067: String locale, String key) throws JspException {
1068: MessageResources resources = retrieveMessageResources(
1069: pageContext, bundle, true);
1070:
1071: Locale userLocale = getUserLocale(pageContext, locale);
1072:
1073: return resources.isPresent(userLocale, key);
1074: }
1075:
1076: /**
1077: * Returns the appropriate MessageResources object for the current module
1078: * and the given bundle.
1079: *
1080: * @param pageContext Search the context's scopes for the resources.
1081: * @param bundle The bundle name to look for. If this is
1082: * <code>null</code>, the default bundle name is
1083: * used.
1084: * @param checkPageScope Whether to check page scope
1085: * @return MessageResources The bundle's resources stored in some scope.
1086: * @throws JspException if the MessageResources object could not be
1087: * found.
1088: */
1089: public MessageResources retrieveMessageResources(
1090: PageContext pageContext, String bundle,
1091: boolean checkPageScope) throws JspException {
1092: MessageResources resources = null;
1093:
1094: if (bundle == null) {
1095: bundle = Globals.MESSAGES_KEY;
1096: }
1097:
1098: if (checkPageScope) {
1099: resources = (MessageResources) pageContext.getAttribute(
1100: bundle, PageContext.PAGE_SCOPE);
1101: }
1102:
1103: if (resources == null) {
1104: resources = (MessageResources) pageContext.getAttribute(
1105: bundle, PageContext.REQUEST_SCOPE);
1106: }
1107:
1108: if (resources == null) {
1109: ModuleConfig moduleConfig = getModuleConfig(pageContext);
1110:
1111: resources = (MessageResources) pageContext.getAttribute(
1112: bundle + moduleConfig.getPrefix(),
1113: PageContext.APPLICATION_SCOPE);
1114: }
1115:
1116: if (resources == null) {
1117: resources = (MessageResources) pageContext.getAttribute(
1118: bundle, PageContext.APPLICATION_SCOPE);
1119: }
1120:
1121: if (resources == null) {
1122: JspException e = new JspException(messages.getMessage(
1123: "message.bundle", bundle));
1124:
1125: saveException(pageContext, e);
1126: throw e;
1127: }
1128:
1129: return resources;
1130: }
1131:
1132: /**
1133: * Save the specified exception as a request attribute for later use.
1134: *
1135: * @param pageContext The PageContext for the current page
1136: * @param exception The exception to be saved
1137: */
1138: public void saveException(PageContext pageContext,
1139: Throwable exception) {
1140: pageContext.setAttribute(Globals.EXCEPTION_KEY, exception,
1141: PageContext.REQUEST_SCOPE);
1142: }
1143:
1144: /**
1145: * Write the specified text as the response to the writer associated with
1146: * this page. <strong>WARNING</strong> - If you are writing body content
1147: * from the <code>doAfterBody()</code> method of a custom tag class that
1148: * implements <code>BodyTag</code>, you should be calling
1149: * <code>writePrevious()</code> instead.
1150: *
1151: * @param pageContext The PageContext object for this page
1152: * @param text The text to be written
1153: * @throws JspException if an input/output error occurs (already saved)
1154: */
1155: public void write(PageContext pageContext, String text)
1156: throws JspException {
1157: JspWriter writer = pageContext.getOut();
1158:
1159: try {
1160: writer.print(text);
1161: } catch (IOException e) {
1162: saveException(pageContext, e);
1163: throw new JspException(messages.getMessage("write.io", e
1164: .toString()));
1165: }
1166: }
1167:
1168: /**
1169: * Write the specified text as the response to the writer associated with
1170: * the body content for the tag within which we are currently nested.
1171: *
1172: * @param pageContext The PageContext object for this page
1173: * @param text The text to be written
1174: * @throws JspException if an input/output error occurs (already saved)
1175: */
1176: public void writePrevious(PageContext pageContext, String text)
1177: throws JspException {
1178: JspWriter writer = pageContext.getOut();
1179:
1180: if (writer instanceof BodyContent) {
1181: writer = ((BodyContent) writer).getEnclosingWriter();
1182: }
1183:
1184: try {
1185: writer.print(text);
1186: } catch (IOException e) {
1187: saveException(pageContext, e);
1188: throw new JspException(messages.getMessage("write.io", e
1189: .toString()));
1190: }
1191: }
1192: }
|