All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.jsftemplating.el.VariableResolver Maven / Gradle / Ivy

/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://jsftemplating.dev.java.net/cddl1.html or
 * jsftemplating/cddl1.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at jsftemplating/cddl1.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */
package com.sun.jsftemplating.el;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Stack;
import java.util.StringTokenizer;

import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;

import com.sun.jsftemplating.layout.descriptors.LayoutComponent;
import com.sun.jsftemplating.layout.descriptors.LayoutComposition;
import com.sun.jsftemplating.layout.descriptors.LayoutElement;
import com.sun.jsftemplating.util.LogUtil;
import com.sun.jsftemplating.util.MessageUtil;
import com.sun.jsftemplating.util.Util;


/**
 *  

VariableResolver is used to parse expressions of the format.

* *

$<type>{<key>}

* *

<type> refers to a registerd {@link VariableResolver.DataSource}, * custom {@link VariableResolver.DataSource}s can be registered via: * {@link #setDataSource(FacesContext ctx, String key, * VariableResolver.DataSource dataSource)}. However, there are many * built-in {@link VariableResolver.DataSource} types that are * pre-registered.

* *

Below are the pre-registered types:

* *
  • {@link #ATTRIBUTE} -- {@link AttributeDataSource}
  • *
  • {@link #APPLICATION} -- {@link ApplicationDataSource}
  • *
  • {@link #BOOLEAN} -- {@link BooleanDataSource}
  • *
  • {@link #CONSTANT} -- {@link ConstantDataSource}
  • *
  • {@link #COPY_PROPERTY} -- {@link CopyPropertyDataSource}
  • *
  • {@link #ESCAPE} -- {@link EscapeDataSource}
  • *
  • {@link #EVAL} -- {@link EvalDataSource}
  • *
  • {@link #HAS_FACET} -- {@link HasFacetDataSource}
  • *
  • {@link #HAS_PROPERTY} -- {@link HasPropertyDataSource}
  • *
  • {@link #INT} -- {@link IntDataSource}
  • *
  • {@link #METHOD_BINDING} -- {@link MethodBindingDataSource}
  • *
  • {@link #METHOD_EXPRESSION} -- {@link MethodExpressionDataSource}
  • *
  • {@link #OPTION} -- {@link OptionDataSource}
  • *
  • {@link #PAGE_SESSION} -- {@link PageSessionDataSource}
  • *
  • {@link #PROPERTY} -- {@link PropertyDataSource}
  • *
  • {@link #REQUEST_PARAMETER} -- * {@link RequestParameterDataSource}
  • *
  • {@link #RESOURCE} -- {@link ResourceBundleDataSource}
  • *
  • {@link #SESSION} -- {@link SessionDataSource}
  • *
  • {@link #STACK_TRACE} -- {@link StackTraceDataSource}
  • *
  • {@link #THIS} -- {@link ThisDataSource}
* * @author Ken Paulsen ([email protected]) */ public class VariableResolver { /** *

This method will substitute variables into the given String, or * return the variable if the substitution is the whole String. This * method looks for the LAST occurance of startToken in the given * String. It then searches from that location (if found) to the * first occurance of typeDelim. The value inbetween is used as the * type of substitution to perform (i.e. request attribute, session, * etc.). It next looks for the next occurance of endToken. The * value inbetween is used as the key passed to the * {@link VariableResolver.DataSource} specified by the type. The * String value from the {@link VariableResolver.DataSource} replaces * the portion of the String from the startToken to the endToken. If * this is the entire String, the Object is returned instead of the * String value. This process is repeated until no more * substitutions are * needed.

* *

This algorithm will accomodate nested variables (e.g. "${A{$x}}"). * It also allows the replacement value itself to contain variables. * Care should be taken to ensure that the replacement String included * does not directly or indirectly refer to itself -- this will cause * an infinite loop.

* *

There is one special case where the string to be evaluated begins * with the startToken and ends with the endToken. In this case, * string substitution is NOT performed. Instead the value of the * request attribute is returned.

* *

This method has a "hack" attached at the end of its processing * which looks at the resulting value and does magic. If the value is * a String that starts with "#{" then it will attempt to locate a * composition parameter matching the next token value and replace or * merge its content with the value. It will replace the value if the * value is in the format #{key}. If there is additonal information * after "key", it will attempt to merge value from the template * parameter with the content after "key". A default value may be * provided for the template parameter by providing a ",default" (e.g. * #{key,default}.

* * @param ctx The FacesContext * @param desc The closest LayoutElement to this string * @param component The assoicated UIComponent * @param string The string to be evaluated. * @param startToken Marks the beginning "$" * @param typeDelim Marks separation of type/variable "{" * @param endToken Marks the end of the variable "}" * * @return The new string with substitutions, or the specified request * attribute value. */ public static Object resolveVariables(FacesContext ctx, LayoutElement desc, UIComponent component, String string, String startToken, String typeDelim, String endToken) { int stringLen = string.length(); int delimIndex; int endIndex; int parenSemi; int startTokenLen = startToken.length(); int delimLen = typeDelim.length(); int endTokenLen = endToken.length(); boolean expressionIsWholeString = false; char firstEndChar = SUB_END.charAt(0); char firstDelimChar = SUB_TYPE_DELIM.charAt(0); char currChar; String type; Object variable; for (int startIndex = string.lastIndexOf(startToken); startIndex != -1; startIndex = string.lastIndexOf(startToken, startIndex - 1)) { // Make sure the startToken isn't escaped if ((startIndex > 0) && (string.charAt(startIndex-1) == ESCAPE_CHAR)) { string = string.substring(0, startIndex-1) // Before '\\' + string.substring(startIndex); // After stringLen--; startIndex--; continue; } // Find first typeDelim delimIndex = string.indexOf(typeDelim, startIndex + startTokenLen); if (delimIndex == -1) { continue; } // Next find the end token parenSemi = 0; endIndex = -1; // Iterate through the string looking for the matching end for (int curr = delimIndex + delimLen; curr < stringLen; ) { // Get the next char... currChar = string.charAt(curr); if ((currChar == firstDelimChar) && typeDelim.equals( string.substring(curr, curr + delimLen))) { // Found the start of another... inc the semi parenSemi++; curr += delimLen; continue; } if ((currChar == firstEndChar) && endToken.equals( string.substring(curr, curr + endTokenLen))) { parenSemi--; if (parenSemi < 0) { // Found the right one! endIndex = curr; break; } // Found one, but this isn't the right one curr += endTokenLen; continue; } curr++; } if (endIndex == -1) { // We didn't find a matching end... continue; } /* // Next find end token endIndex = string.indexOf(endToken, delimIndex+delimLen); matchingIndex = string.lastIndexOf(typeDelim, endIndex); while ((endIndex != -1) && (matchingIndex != delimIndex)) { // We found a endToken, but not the matching one...keep looking endIndex = string.indexOf(endToken, endIndex+endTokenLen); matchingIndex = string.lastIndexOf(typeDelim, matchingIndex-delimLen); } if ((endIndex == -1) || (matchingIndex == -1)) { continue; } */ // Handle special case where string starts with startToken and ends // with endToken (and no replacements inbetween). This is special // because we don't want to convert the attribute to a string, we // want to return it (this allows Object types). if ((startIndex == 0) && (endIndex == string.lastIndexOf(endToken)) && (string.endsWith(endToken))) { // This is the special case... expressionIsWholeString = true; } // Pull off the type... type = string.substring(startIndex + startTokenLen, delimIndex); DataSource ds = getDataSource(ctx, type); if (ds == null) { if ((type.indexOf('<') > -1) || (type.indexOf('&') > -1) || (type.indexOf('[') > -1) || (type.indexOf('#') > -1) || (type.indexOf('$') > -1) || (type.indexOf('%') > -1) || (type.indexOf('(') > -1) || (type.indexOf(')')) > -1) { // Do not consider this a valid EL expression, continue... continue; } throw new IllegalArgumentException("Invalid type '" + type + "' in attribute value: '" + string + "'."); } // Pull off the variable... variable = string.substring(delimIndex + delimLen, endIndex); // Get the value... variable = ds.getValue(ctx, desc, component, (String) variable); if (expressionIsWholeString) { if (variable instanceof String) { // See if we need to do EL magic manipulation... variable = replaceCompParams(ctx, desc, component, (String) variable); } return variable; } // Make new string string = string.substring(0, startIndex) + // Before replacement ((variable == null) ? "" : variable.toString()) + string.substring(endIndex + endTokenLen); // After stringLen = string.length(); } // Return the string return replaceCompParams(ctx, desc, component, string); } /** *

This method implements a "hack" which manipulates the given String * when it starts with "#{". In this case, it will attempt to locate a * composition parameter matching the next token and replace or merge * its content with this String. It will replace the String if the * value is in the format #{key}. If there is additonal content after * "key" (besides a (,) comma), it will attempt to merge the value of * the composition parameter matching key with the content after * "key". A default value may be provided for the template parameter * by providing a ",default" at the end of the expression (e.g. * #{key,default}.

* *

This method does not support replacing template paramters in other * locations within EL.

* * @param ctx The FacesContext. * @param string The String to evaluate and manipulate if necessary. * * @return The same String passed in, or a new one based on method * description. */ private static Object replaceCompParams(FacesContext ctx, LayoutElement desc, UIComponent comp, String string) { // Sanity check if (string == null) { return null; } // First see if we have any params Map globalParams = LayoutComposition.getGlobalParamMap(ctx); if (globalParams.size() == 0) { // No mappings, nothing to do return string; } String token = null; Object value = null; int len, startEL, endEL = 0; int loopStart = 0; char chars[] = string.toCharArray(); boolean foundAtLeastOne = false, isWholeString = false; StringBuilder buff = null; Stack stack = null; while (true) { startEL = findOpenEL(chars, loopStart); // Detect while (startEL != -1) { // Get the next token // FIXME: This is not nearly adequate!! // Need to find all real tokens in #{!(some == expression) || foo} endEL = findChar(chars, startEL + 2, '}', '[', '.', '=', '>', '<', '!', '&', '|', '*', '+', '-', '?', '/', '%', '('); if (endEL == -1) { // Not a match, non-fatal startEL = -1; break; } token = string.substring(startEL + 2, endEL); token = token.trim(); // Check to see if this is template param value = globalParams.get(token); if (value != null) { // We're not done yet! This value is only a flag, we have to // look at the composition stack to be accurate! if (stack == null) { stack = LayoutComposition.getCompositionStack(ctx); } value = LayoutComposition.findTemplateParam(stack, token); break; } startEL = string.indexOf("#{", endEL + 1); } if (startEL == -1) { if (!foundAtLeastOne) { // We didn't find anything to substitute return string; } // Append the rest of the String buff.append(chars, loopStart, chars.length - loopStart); // We're done break; } // Only get here when we find one... foundAtLeastOne = true; if (buff == null) { // Initialize the buffer buff = new StringBuilder(100); } // Check to see if this expression starts at the beginning isWholeString = (startEL == 0); // Add everything before the "#{" buff.append(chars, loopStart, startEL - loopStart); // We got "...#{replaceMe?*", look at the next char to see what to do if (chars[endEL] == '}') { // Swap everything, no merge endEL++; // Move past '}' if (isWholeString && (endEL >= chars.length)) { // Special case, #{} is entire string, return it return resolveVariables(ctx, desc, comp, value); } isWholeString = false; // Ok, we just take the String value of "value" and add it if (value != null) { buff.append(value); } } else { // Merge... we're just going to strip off "#{}" from value and // insert it for now, later we might support more. int start = 0; String strVal = value.toString(); if (strVal.startsWith("#{")) { start = 2; } int end = strVal.length(); if (strVal.charAt(end - 1) == '}') { end--; } // Add merged content... buff.append("#{").append(strVal, start, end); // Find the end of the rest of the #{}... end = findChar(chars, endEL + 1, '}'); if (end == -1) { throw new IllegalArgumentException( "EL has unterminated #{} expression: (" + string + ")"); } buff.append(chars, endEL, ++end - endEL); endEL = end; } loopStart = endEL; } // Replace ${}, return the result... return resolveVariables(ctx, desc, comp, buff.toString()); } /** *

This looks for the first occurance of "#{" in * chars. It returns the index of the starting * character, or -1 if not found.

*/ private static int findOpenEL(char chars[], int idx) { // Allow for a minimum of 3 characters after the # (i.e. {x}) int len = chars.length-3; for (; idx This method searches the given chars from the * startingIndex for any of the characters in * matchChars.

*/ private static int findChar(char chars[], int idx, char ... matchChars) { int len = chars.length; for (; idxLayoutElement
descriptor * @param component The UIComponent * @param value The value to resolve * * @return The result */ public static Object resolveVariables(LayoutElement desc, UIComponent component, Object value) { if (value == null) { return null; } return VariableResolver.resolveVariables( FacesContext.getCurrentInstance(), desc, component, value); } /** * This method replaces the ${..} variables with their attribute values. * It will only do this for Strings and List's that contain Strings. * * @param ctx The FacesContext * @param desc The LayoutElement descriptor * @param component The UIComponent * @param value The value to resolve * * @return The result */ public static Object resolveVariables(FacesContext ctx, LayoutElement desc, UIComponent component, Object value) { if (value == null) { return null; } if (value instanceof String) { value = VariableResolver.resolveVariables( ctx, desc, component, (String) value, VariableResolver.SUB_START, VariableResolver.SUB_TYPE_DELIM, VariableResolver.SUB_END); } else if (value instanceof List) { // Create a new List b/c invalid to change shared List List list = ((List) value); List newList = new ArrayList(list.size()); Iterator it = list.iterator(); while (it.hasNext()) { newList.add(VariableResolver.resolveVariables( ctx, desc, component, it.next())); } return newList; } else if (value instanceof Object []) { // Create a new array b/c invalid to change shared array Object [] arr = (Object []) value; Object [] newArr = new Object[arr.length]; int idx = 0; for (Object obj : arr) { newArr[idx++] = VariableResolver.resolveVariables( ctx, desc, component, obj); } return newArr; } return value; } /** *

This method looks up the requested * {@link VariableResolver.DataSource} by the given key. * * @param key The key identifying the desired * {@link VariableResolver.DataSource} * * @return The requested {@link VariableResolver.DataSource} */ public static VariableResolver.DataSource getDataSource(FacesContext ctx, String key) { // Get the Map... and pull off the value (may be null) return VariableResolver.getDataSourceMap(ctx).get(key); } /** *

Provides access to the application-scoped Map which stores the * {@link VariableResolver#DataSource}'s for this application.

*/ private static Map getDataSourceMap(FacesContext ctx) { if (ctx == null) { ctx = FacesContext.getCurrentInstance(); } Map dataSourceMap = null; if (ctx != null) { dataSourceMap = (Map) ctx.getExternalContext().getApplicationMap().get(VR_APP_KEY); } if (dataSourceMap == null) { // 1st time... initialize it dataSourceMap = new HashMap(); AttributeDataSource attrDS = new AttributeDataSource(); dataSourceMap.put("", attrDS); dataSourceMap.put(ATTRIBUTE, attrDS); dataSourceMap.put(APPLICATION, new ApplicationDataSource()); dataSourceMap.put(COPY_PROPERTY, new CopyPropertyDataSource()); dataSourceMap.put(OPTION, new OptionDataSource()); dataSourceMap.put(PAGE_SESSION, new PageSessionDataSource()); dataSourceMap.put(PROPERTY, new PropertyDataSource()); dataSourceMap.put(HAS_PROPERTY, new HasPropertyDataSource()); dataSourceMap.put(HAS_FACET, new HasFacetDataSource()); dataSourceMap.put(SESSION, new SessionDataSource()); dataSourceMap.put(STACK_TRACE, new StackTraceDataSource()); dataSourceMap.put(REQUEST_PARAMETER, new RequestParameterDataSource()); // dataSourceMap.put(DISPLAY, new DisplayFieldDataSource()); dataSourceMap.put(THIS, new ThisDataSource()); dataSourceMap.put(ESCAPE, new EscapeDataSource()); dataSourceMap.put(EVAL, new EvalDataSource()); dataSourceMap.put(INT, new IntDataSource()); dataSourceMap.put(BOOLEAN, new BooleanDataSource()); dataSourceMap.put(CONSTANT, new ConstantDataSource()); dataSourceMap.put(RESOURCE, new ResourceBundleDataSource()); dataSourceMap.put(METHOD_BINDING, new MethodBindingDataSource()); dataSourceMap.put(METHOD_EXPRESSION, new MethodExpressionDataSource()); if (ctx != null) { ctx.getExternalContext().getApplicationMap().put( VR_APP_KEY, dataSourceMap); } } // Return the map... return dataSourceMap; } /** *

This method sets the given {@link VariableResolver.DataSource} to * be used for $[type]{...} when key matches type.

* * @param key The key identifying the * {@link VariableResolver.DataSource} * @param dataSource The {@link VariableResolver.DataSource} */ public static synchronized void setDataSource(FacesContext ctx, String key, VariableResolver.DataSource dataSource) { // Get the Map... and pull off the value (may be null) // NOTE: This is not thread safe.... although this is very rare VariableResolver.getDataSourceMap(ctx).put(key, dataSource); } /** *

This interface defines a String substitution data source. This * is used to retrieve values when a $<type>{<data>} is * encountered within a parameter value.

* *

Implementations of this interface may register themselves * statically to extend the capabilities of the ${} substitution * mechanism.

*/ public interface DataSource { /** *

This method should return the resolved value based on the * given key and contextual information.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key); } /** *

This {@link VariableResolver.DataSource} provides access to * Application-scoped attributes. It uses the data portion of the * substitution String as a key to the Map.

*/ public static class ApplicationDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { return ctx.getExternalContext().getApplicationMap().get(key); } } /** *

This {@link VariableResolver.DataSource} provides access to * HttpRequest attributes. It uses the data portion of the * substitution String as a key to the HttpRequest attribute Map.

*/ public static class AttributeDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { return ctx.getExternalContext().getRequestMap().get(key); } } /** *

This {@link VariableResolver.DataSource} provides access to * "option" values that are set on a {@link LayoutComponent}. It * uses the data portion of the substitution String as a key to the * {@link LayoutComponent}'s options. If a value is not found, it * will walk up the LayoutComponent's parents looking for a defined * value.

*/ public static class OptionDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. (null) if not found. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { Object value = null; while ((value == null) && (desc != null)) { if (desc instanceof LayoutComponent) { value = ((LayoutComponent) desc).getEvaluatedOption(ctx, key, component); } desc = desc.getParent(); } return value; } } /** *

This {@link VariableResolver.DataSource} provides access to * PageSession attributes. It uses the data portion of the * substitution String as a key to the PageSession attribute Map.

*/ public static class PageSessionDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { Map map = PageSessionResolver.getPageSession(ctx, ctx.getViewRoot()); Serializable value = null; if (map != null) { value = map.get(key); } return value; } } /** *

This {@link VariableResolver.DataSource} provides access to * HttpRequest Parameters. It uses the data portion of the * substitution String as a key to the HttpRequest Parameter Map.

*/ public static class RequestParameterDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { return ctx.getExternalContext().getRequestParameterMap().get(key); } } /** *

This {@link VariableResolver.DataSource} copies * UIComponent properties. It uses the data portion of * the substitution String as a key to the UIComponent's properties * via the attribute map. If the property is null, it will attempt to * look at the parent's properties.

*/ public static class CopyPropertyDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { return findPropertyValue(ctx, desc, component, key, true); } public Object findPropertyValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key, boolean checkVE) { if (component == null) { return ""; } // Check to see if we should walk up the tree or not int idx = key.indexOf(','); boolean walk = false; if (idx > 0) { walk = Boolean.valueOf(key.substring(idx + 1).trim()).booleanValue(); key = key.substring(0, idx); } Object value = null; if (checkVE) { value = component.getValueExpression(key); if (value == null) { value = component.getAttributes().get(key); } } else { value = component.getAttributes().get(key); } if (walk) { while ((value == null) && (component.getParent() != null)) { component = component.getParent(); if (checkVE) { value = component.getValueExpression(key); if (value == null) { value = component.getAttributes().get(key); } } else { value = component.getAttributes().get(key); } } } /* if (LogUtil.finestEnabled()) { // Trace information LogUtil.finest(this, "RESOLVING ('" + key + "') for ('" + component.getId() + "'): '" + value + "'"); } */ return value; } } /** *

This {@link VariableResolver.DataSource} provides access to * UIComponent Properties. It uses the data portion of the * substitution String as a key to the UIComponent's properties via * the attribute Map. If the property is null, it will attempt to * look at the parent's properties.

*/ public static class PropertyDataSource extends CopyPropertyDataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { return findPropertyValue(ctx, desc, component, key, false); } } /** *

This {@link VariableResolver.DataSource} tests if the given * property exists on the UIComponent. It uses the data portion of * the substitution String as a key to the UIComponent's properties * via the attribute Map.

*/ public static class HasPropertyDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { boolean hasKey = component.getAttributes().containsKey(key); if (!hasKey) { // Check the getter... JSF sucks when wrt attrs vs. props if ((component.getValueExpression(key) != null) || (component.getAttributes().get(key) != null)) { hasKey = true; } } if (!hasKey && (desc instanceof LayoutComponent)) { // In some cases, the component is a TemplateComponent child return getValue( ctx, desc.getParent(), component.getParent(), key); } return Boolean.valueOf(hasKey); } } /** *

This {@link VariableResolver.DataSource} tests if the given facet * exists on the UIComponent. It uses the data portion of the * substitution String as a key to the UIComponent's facets.

*/ public static class HasFacetDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { boolean hasFacet = component.getFacets().containsKey(key); if (!hasFacet && (desc instanceof LayoutComponent)) { // In some cases, the component is a TemplateComponent child return getValue( ctx, desc.getParent(), component.getParent(), key); } return Boolean.valueOf(hasFacet); } } /** *

This {@link VariableResolver.DataSource} simply returns the key * that it is given. This is useful for supplying ${}'s around the * string you wish to mark as a string. If not used, characters such * as '=' will be interpretted as a separator causing your string to * be split -- which can be very undesirable. Mostly useful in "if" * statements.

*/ public static class EscapeDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { return key; } } /** *

This {@link VariableResolver.DataSource} evaluates the given * boolean expression.

*/ public static class EvalDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { PermissionChecker checker = new PermissionChecker(desc, component, key); return Boolean.valueOf(checker.hasPermission()); } } /** *

This {@link VariableResolver.DataSource} converts the given * key to a Boolean. This is needed because * JSF does not do this for you. When you call * UIComponent.getAttributes().put(key, value), * value is expected to be the correct type. Often * Boolean types are needed. This * {@link VariableResolver.DataSource} provides a means to supply a * Boolean value.

*/ public static class BooleanDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { return Boolean.valueOf(key); } } /** *

This {@link VariableResolver.DataSource} converts the given * key to an Integer. This is needed * because JSF does not do this for you. When you call * UIComponent.getAttributes().put(key, value), * value is expected to be the correct type. Often * Integer types are needed. This * {@link VariableResolver.DataSource} provides a means to supply an * Integer value.

*/ public static class IntDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { return Integer.valueOf(key); } } /** *

This {@link VariableResolver.DataSource} allows access to constants * in java classes. It expects the key to be a fully qualified Java * classname plus the variable name. Example:

* *

$constant{java.lang.Integer.MAX_VALUE}

*/ public static class ConstantDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { // First check to see if we've already found the value before. Object value = constantMap.get(key); if (value == null) { // Not found, lets resolve it, duplicate the old Map to avoid // sync problems Map map = new HashMap(constantMap); value = resolveValue(map, key); // Replace the shared Map w/ this new one. constantMap = map; } return value; } /** *

This method resolves key. Key is expected to be in the * format:

* *

some.package.Class.STATIC_VARIBLE

* *

This method will first resolve Class. It will then walk * through all its variables adding each static final variable to * the Map.

* * @param map The map to add variables to * @param key The fully qualified CONSTANT name * * @return The value of the CONSTANT, or null if not found */ private Object resolveValue(Map map, String key) { int lastDot = key.lastIndexOf('.'); if (lastDot == -1) { throw new IllegalArgumentException("Unable to resolve '" + key + "' in $constant{" + key + "}. '" + key + "' must be a " + "fully qualified classname plus the constant name."); } // Get the classname / constant name String className = key.substring(0, lastDot); // Add all constants to the Map try { addConstants(map, Util.loadClass(className, key)); } catch (ClassNotFoundException ex) { RuntimeException iae = new IllegalArgumentException("'" + className + "' was not found! This must be a valid " + "classname. This was found in expression $constant{" + key + "}."); iae.initCause(ex); throw iae; } // The constant hopefully is in the Map now, null if not return map.get(key); } /** * This method adds all constants in the given class to the Map. The * Map key will be the fully qualified class name, plus a '.', plus * the constant name. * * @param map Map to store cls * @param cls The Class to store in map */ private void addConstants(Map map, Class cls) { // Get the class name String className = cls.getName(); // Get the fields Field[] fields = cls.getFields(); // Add the static final fields to the Map Field field = null; for (int count = 0; count < fields.length; count++) { field = fields[count]; if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) { try { map.put(className + '.' + field.getName(), field.get(null)); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } } } } /** * This embedded Map caches constant value lookups. It is static and * is shared by all users. */ private static Map constantMap = new HashMap(); } /** *

This {@link VariableResolver.DataSource} creates a MethodExpression * from the supplied key. Example:

* *

$methodExpression{#{bean.key}}

*/ public static class MethodExpressionDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { Class [] args = EMPTY_CLASS_ARRAY; key = key.trim(); int commaIdx = key.lastIndexOf(','); if (commaIdx != -1) { if (key.endsWith("true")) { args = ACTION_ARGS; key = key.substring(0, commaIdx); } else if (key.endsWith("false")) { key = key.substring(0, commaIdx); } } return ctx.getApplication().getExpressionFactory(). createMethodExpression( ctx.getELContext(), key, Object.class, args); } } /** *

This {@link VariableResolver.DataSource} creates a MethodBinding * from the supplied key. Example:

* *

$methodBinding{#{bean.key}}

*/ public static class MethodBindingDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { return ctx.getApplication().createMethodBinding(key, ACTION_ARGS); } } /** *

This {@link VariableResolver.DataSource} allows access to resource * bundle keys. It expects the key to be a resource bundle key plus a * '.' then the actual resouce bundle key Example:

* *

$resource{bundleID.bundleKey}

* *

The bundleID should not contain '.' characters. The bundleKey * may.

*/ public static class ResourceBundleDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { // Get the Request attribute key int separator = key.indexOf("."); if (separator == -1) { throw new IllegalArgumentException("'" + key + "' is not in format: \"[bundleID].[bundleKey]\"!"); } String value = key.substring(0, separator); // Get the Resource Bundle Object obj = ctx.getExternalContext().getRequestMap().get(value); // Make sure we have something... if (obj == null) { // Only check the request scope b/c the RB is request specific // Should we throw an exception? For now return the key return key; } // Make sure its a RB if (!(obj instanceof ResourceBundle)) { throw new IllegalArgumentException("\"" + value + "\" in: \"" + SUB_START + RESOURCE + SUB_TYPE_DELIM + key + SUB_END + "\" did not resolve to a ResourceBundle! Found: \"" + obj.getClass().getName() + "\" instead. (toString(): " + obj.toString() + ")"); } ResourceBundle bundle = (ResourceBundle) obj; // Return the result of the ResouceBundle lookup String str = null; int argSep = key.indexOf(",", separator); try { if (argSep > -1) { str = bundle.getString(key.substring(separator + 1, argSep)); } else { str = bundle.getString(key.substring(separator + 1)); } if (str == null) { str = key; } else { // Parse arguments if (argSep > -1) { StringTokenizer st = new StringTokenizer(key.substring(argSep), ","); String[] tokens = new String[st.countTokens()]; int i = 0; while (st.hasMoreTokens()) { tokens[i++] = st.nextToken().trim(); } str = MessageUtil.getFormattedMessage(str, tokens); } } } catch (MissingResourceException ex) { if (LogUtil.configEnabled()) { LogUtil.config("Unable to find key: '" + key.substring(separator + 1) + "' in ResourceBundle '" + value + "'. Perhaps this needs to be added?", ex); } else if (LogUtil.infoEnabled()) { // Info log level, don't be verbose, just display a benign // warning. LogUtil.info("JSFT0003", new Object[] {key.substring(separator + 1), value}); } str = key; } return str; } } /** *

This {@link VariableResolver.DataSource} provides access to * HttpSession attributes. It uses the data portion of the * substitution String as a key to the HttpSession Map.

*/ public static class SessionDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { return ctx.getExternalContext().getSessionMap().get(key); } } /** *

This {@link VariableResolver.DataSource} returns a strack trace * (as a String) from the current Thread. The data * portion of the "substitution String" is used as a message * prefixing the trace.

*/ public static class StackTraceDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param component The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { // Get the trace information StackTraceElement[] trace = Thread.currentThread().getStackTrace(); int len = trace.length; // Create a String w/ this info... StringBuffer buf = new StringBuffer(key + "\n"); for (int idx = 0; idx < len; idx++) { buf.append(trace[idx] + "\n"); } // Return it. return buf.toString(); } } /** *

This {@link VariableResolver.DataSource} provides access to * DisplayField values. It uses the data portion of the substitution * String as the DisplayField name to find. This is a non-qualified * DisplayField name. It will walk up the View tree starting at the * View object cooresponding to the LayoutElement which contained this * expression. At each ContainerView, it will look for a child with * a matching name.

public static class DisplayFieldDataSource implements DataSource { public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent component, String key) { while (desc != null) { View view = desc.getView(ctx); if (view instanceof ContainerView) { View child = null; //FIXME: use a better way to find if 'key' is a child of 'view' try { child = (((ContainerView)(view)).getChild(key)); } catch (Exception ex) { } if (child != null) { return ((ContainerView) view).getDisplayFieldValue(key); } } desc = desc.getParent(); } return null; } } */ /** *

This class provides an implementation for the syntax $this{xyz} * where xyz can be any of the following.

* *
  • component -- Current UIComponent
  • *
  • clientId -- Current UIComponent's client id
  • *
  • id -- Current UIComponent's id
  • *
  • layoutElement -- Current {@link LayoutElement}
  • *
  • parent -- Parent UIComponent
  • *
  • parentId -- Parent UIComponent's client id
  • *
  • parentLayoutElement -- Parent {@link LayoutElement}
  • *
  • namingContainer -- Nearest NamingContainer
  • *
  • valueBinding -- ValueBinding representing the * UIComponent
  • *
  • children -- Current UIComponent's children
  • *
*/ public static class ThisDataSource implements DataSource { /** *

See class JavaDoc.

* * @param ctx The FacesContext * @param desc The LayoutElement * @param comp The UIComponent * @param key The key used to obtain information from this * DataSource. * * @return The value resolved from key. */ public Object getValue(FacesContext ctx, LayoutElement desc, UIComponent comp, String key) { Object value = null; if ((key.equalsIgnoreCase(CLIENT_ID)) || (key.length() == 0)) { value = comp.getClientId(ctx); } else if (key.equalsIgnoreCase(ID)) { value = comp.getId(); } else if (key.equalsIgnoreCase(CHILDREN)) { value = comp.getChildren(); } else if (key.equalsIgnoreCase(COMPONENT)) { value = comp; } else if (key.equalsIgnoreCase(LAYOUT_ELEMENT)) { value = desc; } else if (key.equalsIgnoreCase(PARENT_ID)) { value = comp.getParent().getId(); } else if (key.equalsIgnoreCase(PARENT_CLIENT_ID)) { value = comp.getParent().getClientId(ctx); } else if (key.equalsIgnoreCase(PARENT)) { value = comp.getParent(); } else if (key.equalsIgnoreCase(PARENT_LAYOUT_ELEMENT)) { value = desc.getParent(); } else if (key.equalsIgnoreCase(NAMING_CONTAINER)) { for (value = comp.getParent(); value != null; value = ((UIComponent) value).getParent()) { if (value instanceof NamingContainer) { break; } } } else if (key.equalsIgnoreCase(VALUE_BINDING)) { // Walk backward up the tree generate the path Stack stack = new Stack(); String id = null; // FIXME: b/c of a bug, the old behavior actually returned the // FIXME: parent component... the next line is here to persist // FIXME: this behavior b/c some code depends on this, fix this // FIXME: when you have a chance. comp = comp.getParent(); while ((comp != null) && !(comp instanceof UIViewRoot)) { id = comp.getId(); if (id == null) { // Generate an id based on the clientId id = comp.getClientId(ctx); id = id.substring(id.lastIndexOf( NamingContainer.SEPARATOR_CHAR) + 1); } stack.push(id); comp = comp.getParent(); } StringBuffer buf = new StringBuffer(); buf.append("view"); while (!stack.empty()) { buf.append("['" + stack.pop() + "']"); } value = buf.toString(); } else { throw new IllegalArgumentException("'" + key + "' is not valid in $this{" + key + "}."); } return value; } /** *

Defines "children" in $this{children}. Returns the * UIComponent's children.

*/ public static final String CHILDREN = "children"; /** *

Defines "component" in $this{component}. Returns the * UIComponent object.

*/ public static final String COMPONENT = "component"; /** *

Defines "clientId" in $this{clientId}. Returns * the String representing the client id for the UIComponent.

*/ public static final String CLIENT_ID = "clientId"; /** *

Defines "id" in $this{id}. Returns the String representing * the id for the UIComponent.

*/ public static final String ID = "id"; /** *

Defines "layoutElement" in $this{layoutElement}. Returns * the LayoutElement.

*/ public static final String LAYOUT_ELEMENT = "layoutElement"; /** *

Defines "parent" in $this{parent}. Returns the * parent UIComponent object.

*/ public static final String PARENT = "parent"; /** *

Defines "parentId" in $this{parentId}. Returns the * parent UIComponent object's Id.

*/ public static final String PARENT_ID = "parentId"; /** *

Defines "parentClientId" in $this{parentClientId}. Returns the * parent UIComponent object's client Id.

*/ public static final String PARENT_CLIENT_ID = "parentClientId"; /** *

Defines "parentLayoutElement" in $this{parentLayoutElement}. * Returns the parent LayoutElement.

*/ public static final String PARENT_LAYOUT_ELEMENT = "parentLayoutElement"; /** *

Defines "namingContainer" in $this{namingContainer}. Returns * the nearest naming container object (i.e. the form).

*/ public static final String NAMING_CONTAINER = "namingContainer"; /** *

Defines "valueBinding" in $this{valueBinding}. Returns * a ValueBinding to this UIComponent.

*/ public static final String VALUE_BINDING = "valueBinding"; } /** * The main function for this class provides some simple test cases. * * @param args The commandline arguments. */ public static void main(String[] args) { String test = null; String good = null; test = "" + VariableResolver.resolveVariables(null, null, null, "$escape($escape(LayoutElement))", "$", "(", ")"); good = "LayoutElement"; System.out.println("Expected Result: '" + good + "'"); System.out.println(" Result: '" + test + "'"); if (!test.equals(good)) { System.out.println("FAILED!!!!"); } test = "" + VariableResolver.resolveVariables(null, null, null, "$escape($escape(EEPersistenceManager))", "$", "(", ")"); good = "EEPersistenceManager"; System.out.println("Expected Result: '" + good + "'"); System.out.println(" Result: '" + test + "'"); if (!test.equals(good)) { System.out.println("FAILED!!!!"); } test = "" + VariableResolver.resolveVariables(null, null, null, "$es$cape$escape(EEPersistenceManager))", "$", "(", ")"); good = "$es$capeEEPersistenceManager)"; System.out.println("Expected Result: '" + good + "'"); System.out.println(" Result: '" + test + "'"); if (!test.equals(good)) { System.out.println("FAILED!!!!"); } test = "" + VariableResolver.resolveVariables(null, null, null, "$escape($escapeEEP$ersistenceManager))", "$", "(", ")"); good = "$escapeEEP$ersistenceManager)"; System.out.println("Expected Result: '" + good + "'"); System.out.println(" Result: '" + test + "'"); if (!test.equals(good)) { System.out.println("FAILED!!!!"); } test = "" + VariableResolver.resolveVariables(null, null, null, "$escape($escape(EEPersistenceManager)))", "$", "(", ")"); good = "EEPersistenceManager)"; System.out.println("Expected Result: '" + good + "'"); System.out.println(" Result: '" + test + "'"); if (!test.equals(good)) { System.out.println("FAILED!!!!"); } test = "" + VariableResolver.resolveVariables(null, null, null, "$escape($escape(EEPersistenceManager())", "$", "(", ")"); good = "$escape(EEPersistenceManager()"; System.out.println("Expected Result: '" + good + "'"); System.out.println(" Result: '" + test + "'"); if (!test.equals(good)) { System.out.println("FAILED!!!!"); } test = "" + VariableResolver.resolveVariables(null, null, null, "$escape($escape($escape(EEPersistenceManager()))==$escape(" + "EEPersistenceManager()))", "$", "(", ")"); good = "EEPersistenceManager()==EEPersistenceManager()"; System.out.println("Expected Result: '" + good + "'"); System.out.println(" Result: '" + test + "'"); if (!test.equals(good)) { System.out.println("FAILED!!!!"); } test = "" + VariableResolver.resolveVariables(null, null, null, "$escape($escape($escape(EEPersistenceManager()))==$escape(" + "EEPersistenceManager()))", "$", "(", ")"); good = "EEPersistenceManager()==EEPersistenceManager()"; System.out.println("Expected Result: '" + good + "'"); System.out.println(" Result: '" + test + "'"); if (!test.equals(good)) { System.out.println("FAILED!!!!"); } /* for (int x = 0; x < 100000; x++) { System.out.println("" + VariableResolver.resolveVariables( null, null, null, "$escape($escape(EEPers" + x + "istenceManager()))==$escape(" + "EEPersistenceManager())", "$", "(", ")")); } */ } /** *

Defines "attribute" in $attribute{...}. This allows you to * retrieve an HttpRequest attribute.

*/ public static final String ATTRIBUTE = "attribute"; /** *

Defines "application" in $application{...}. This allows you to * retrieve an application-scoped attribute.

*/ public static final String APPLICATION = "application"; /** *

Defines "copyProperty" in $copyProperty{...}. This allows you to * copy a property from the current UIComponent (or search for the * property to copy by passing in "propName,true".

*/ public static final String COPY_PROPERTY = "copyProperty"; /** *

Defines "option" in $option{...}. This allows you to obtain an * "option" that is defined in a {@link LayoutComponent}.

*/ public static final String OPTION = "option"; /** *

Defines "pageSession" in $pageSession{...}. This allows you to * retrieve a PageSession attribute.

*/ public static final String PAGE_SESSION = "pageSession"; /** *

Defines "property" in $property{...}. This allows you to * retrieve a property from the UIComponent.

*/ public static final String PROPERTY = "property"; /** *

Defines "hasProperty" in $hasProperty{...}. This allows you to * see if a property from the UIComponent exists.

*/ public static final String HAS_PROPERTY = "hasProperty"; /** *

Defines "hasFacet" in $hasFacet{...}. This allows you to * see if a facet from the UIComponent exists.

*/ public static final String HAS_FACET = "hasFacet"; /** *

Defines "session" in $session{...}. This allows you to retrieve * an HttpSession attribute. */ public static final String SESSION = "session"; /** *

Defines "stackTrace" in $stackTrace{...}. This allows you to get * a stack trace from the current Thread. */ public static final String STACK_TRACE = "stackTrace"; /** *

Defines "requestParameter" in $requestParameter{...}. This allows * you to retrieve a HttpRequest parameter (QUERY_STRING * parameter).

*/ public static final String REQUEST_PARAMETER = "requestParameter"; /** *

Defines "display" in $display{...}. This allows you to retrive * a DisplayField value.

public static final String DISPLAY = "display"; */ /** *

Defines "this" in $this{...}. This allows you to retrieve a * number of different objects related to the relative placement of * this expression.

* * @see ThisDataSource */ public static final String THIS = "this"; /** *

Defines "escape" in $escape{...}. This allows some reserved * characters to be escaped in "if" attributes. Such as '=' or * '|'.

*/ public static final String ESCAPE = "escape"; /** *

Defines "eval" in $eval{...}. This allows a boolean expression to * be evaulated.

*/ public static final String EVAL = "eval"; /** *

Defines "boolean" in $boolean{...}. This converts the given * String to a Boolean.

*/ public static final String BOOLEAN = "boolean"; /** *

Defines "browser" in $browser{...}. This checks properties of the * browser that sent the request.

*/ public static final String BROWSER = "browser"; /** *

Defines "int" in $int{...}. This converts the given String to an * Integer.

*/ public static final String INT = "int"; /** *

Defines "methodBinding" in $methodBinding{...}. This allows * MethodBindings to be created.

*/ public static final String METHOD_BINDING = "methodBinding"; /** *

Defines "methodExpression" in $methodExpression{...}. This allows * MethodExpressions to be created.

*/ public static final String METHOD_EXPRESSION = "methodExpression"; /** *

Defines "constant" in $constant{...}. This allows constants * in java classes to be accessed.

*/ public static final String CONSTANT = "constant"; /** *

Defines "resource" in $resource{...}. This allows resource * to be accessed.

*/ public static final String RESOURCE = "resource"; /** * Constant defining the arguments required for a Action MethodBinding. */ private static final Class[] ACTION_ARGS = {ActionEvent.class}; /** * Empty Class[] for methods that take no arguments. */ private static final Class[] EMPTY_CLASS_ARRAY = {}; /** *

Application scope key to hold the VariableResolver DataSources.

*/ public static final String VR_APP_KEY = "__jsft_vrds_map"; /** * Escape character. */ public static final char ESCAPE_CHAR = '\\'; /** * The '$' character marks the beginning of a substituion in a String. */ public static final String SUB_START = "$"; /** * The '(' character marks the beginning of the data content of a String * substitution. */ public static final String SUB_TYPE_DELIM = "{"; /** * The ')' character marks the end of the data content for a String * substitution. */ public static final String SUB_END = "}"; }