/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * $Header:$ */ package org.apache.beehive.netui.tags.naming; // java imports import org.apache.beehive.netui.script.Expression; import org.apache.beehive.netui.script.ExpressionEvaluationException; import org.apache.beehive.netui.script.ExpressionEvaluator; import org.apache.beehive.netui.script.ExpressionEvaluatorFactory; import org.apache.beehive.netui.script.common.IDataAccessProvider; import org.apache.beehive.netui.util.logging.Logger; import javax.servlet.jsp.tagext.SimpleTagSupport; import javax.servlet.jsp.tagext.Tag; import java.util.List; // external imports /** * A {@link INameInterceptor} that is used to rewrite names which * reference the container databinding context. This * INameInterceptor is for use by tags that render form-updatable HTML * elements. If the dataSource attribute of the tag references a * container binding context, the name must be qualified * into a real path down a bean / property hierarchy in order to * correctly update that value on a POST. This INameInterceptor * rewrites that name using the given name and the hierarchy of * {@link org.apache.beehive.netui.script.common.IDataAccessProvider} tags in a JSP page. */ public class IndexedNameInterceptor implements INameInterceptor { private static final Logger _logger = Logger.getInstance(IndexedNameInterceptor.class); /** * Rewrite an expression into a fully-qualified reference to a specific JavaBean property * on an object. * @param name the expression to rewrite * @param currentTag the current JSP tag that can be used as the leaf for walking up * to find parent tags that provide information used to * rewrite the expression. */ public final String rewriteName(String name, Tag currentTag) throws ExpressionEvaluationException { if (_logger.isDebugEnabled()) _logger.debug("rewrite expression \"" + name + "\""); IDataAccessProvider dap = getCurrentProvider(currentTag); // if the DAP is null, there is no rewriting to do if (dap == null) return name; // given a hierarchy of "container.container.container.item.someProp", the correct parent needs // to be found so that expression rewriting can happen correctly. // // ensure that this expression contains container.item Expression parsed = getExpressionEvaluator().parseExpression(name); assert parsed != null; int containerCount = 0; List tokens = parsed.getTokens(); for (int i = 0; i < tokens.size(); i++) { String tok = tokens.get(i).toString(); if (i == 0) { if (!tok.equals("container")) break; else continue; } // this skips the "current" IDataAccessProvider else if (tok.equals("container")) containerCount++; else if (tok.equals("item")) break; } if (_logger.isDebugEnabled()) _logger.debug("container parent count: " + containerCount); // now walk up the DataAccessProvier hierarchy until the top-most parent is found // the top-most parent is the first one that does not reference "container.item" but // is bound directly to a specific object such as "actionForm" or "pageFlow". This // handles the case where a set of nested IDataAccessProvider tags are "skipped" by // an expression like "container.container.container.item.foo". In order to find // the correct root to start rewriting the names, one needs to walk up three // DAPs in order to find the correct root from which to start. // // In general, containerCount is zero here for the "container.item.foo" case. for (int i = 0; i < containerCount; i++) { dap = dap.getProviderParent(); } // now, the top-most DAP parent is known assert dap != null; // strip off the "container.item" from the expression that is being rewritten // this should be two tokens into the expression. if (containerCount > 0) { name = parsed.getExpression(containerCount); } // now, change the binding context of the parent DAP hierarchy to create a // String that looks like "actionForm.customers[42].order[12].lineItem[2].name" // note, this is done without using the expression that was passed-in and // is derived entirely from the IDataAccessProvider parent hierarchy. String parentNames = rewriteNameInternal(dap); if (_logger.isDebugEnabled()) _logger.debug("name hierarchy: " + parentNames + " name: " + name); // with a newly re-written expression prefix, substitute this fully-qualified binding // string into the given expression for "container.item". String newName = changeContext(name, "container.item", parentNames, dap.getCurrentIndex()); if (_logger.isDebugEnabled()) _logger.debug("rewrittenName: " + newName); return newName; } /** * A default method to find the "current" IDataAccessProvider. This method is * left as non-final so that the implementation here can be tested * outside of a servlet container. */ protected IDataAccessProvider getCurrentProvider(Tag tag) { return (IDataAccessProvider) SimpleTagSupport.findAncestorWithClass(tag, IDataAccessProvider.class); } /** * Rewrite a parent IDataAccessProvider's dataSource to be fully qualified. * * "container.container.container.container.item.foo" -> "DS1.DS2.DS3.DS4.foo" */ private final String rewriteNameInternal(IDataAccessProvider dap) throws ExpressionEvaluationException { if (_logger.isDebugEnabled()) _logger.debug("assign index to name: " + dap.getDataSource()); Expression parsedDataSource = getExpressionEvaluator().parseExpression(dap.getDataSource()); assert parsedDataSource != null; // @todo: perf boolean isContainerBound = (parsedDataSource.getTokens().get(0)).toString().equals("container"); // rewrite the name of the current IDataAccessProvider. String parentName = null; // if the current DAP has a parent IDataAccessProvider, rewrite the name of the parent if (dap.getProviderParent() != null) parentName = rewriteNameInternal(dap.getProviderParent()); // if the current DAP has no parent, or it does not reference the "container." binding context, // we've found the "root" IDataAccessProvider else if (dap.getProviderParent() == null || (dap.getProviderParent() != null && !isContainerBound)) { return dap.getDataSource(); } // now, we've found the root and can start rewriting the expressions throughout // the rest of the DAP hierarchy if (_logger.isDebugEnabled()) { _logger.debug("changeContext: DAP.dataSource=" + dap.getDataSource() + " oldContext=container newContext=" + parentName + " currentIndex=" + dap.getProviderParent().getCurrentIndex() + " parentName is container: " + isContainerBound); } String retVal = null; String ds = dap.getDataSource(); // If the current DAP's dataSource is "container.item", the binding context needs to change to that // of the parent. This case should only occur for the last token -- the "name" passed into // the method. Oterwise, just replace the "container" to that of the parent. Both are // qualified with the DAP's current index so that "actionForm.customers" becomes // "actionForm.customers[12]". boolean isContainerItemBound = false; if (isContainerBound && (parsedDataSource.getTokens().get(1)).toString().equals("item")) isContainerItemBound = true; if (isContainerItemBound) retVal = changeContext(ds, "container.item", parentName, dap.getProviderParent().getCurrentIndex()); else retVal = changeContext(ds, "container", parentName, dap.getProviderParent().getCurrentIndex()); if (_logger.isDebugEnabled()) _logger.debug("fully-qualified binding expression: \"" + retVal + "\""); return retVal; } protected ExpressionEvaluator getExpressionEvaluator() { return ExpressionEvaluatorFactory.getInstance(); } private final String changeContext(String dataSource, String oldContext, String newContext, int index) throws ExpressionEvaluationException { try { return getExpressionEvaluator().changeContext(dataSource, oldContext, newContext, index); } catch (ExpressionEvaluationException ee) { if (_logger.isErrorEnabled()) _logger.error("An error occurred changing the binding context of the expression \"" + dataSource + "\". Cause: " + ee, ee); throw ee; } } }