224 lines
9.8 KiB
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;
|
|
}
|
|
}
|
|
}
|