JdbcMapper/beehive-netui-tags/src/main/java/org/apache/beehive/netui/tags/naming/IndexedNameInterceptor.java

224 lines
9.8 KiB
Java

/*
* 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 <code>container</code> databinding context. This
* INameInterceptor is for use by tags that render form-updatable HTML
* elements. If the dataSource attribute of the tag references a
* <code>container</code> 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;
}
}
}