001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.tags.naming;
020:
021: // java imports
022:
023: import org.apache.beehive.netui.script.Expression;
024: import org.apache.beehive.netui.script.ExpressionEvaluationException;
025: import org.apache.beehive.netui.script.ExpressionEvaluator;
026: import org.apache.beehive.netui.script.ExpressionEvaluatorFactory;
027: import org.apache.beehive.netui.script.common.IDataAccessProvider;
028: import org.apache.beehive.netui.util.logging.Logger;
029:
030: import javax.servlet.jsp.tagext.SimpleTagSupport;
031: import javax.servlet.jsp.tagext.Tag;
032: import java.util.List;
033:
034: // external imports
035:
036: /**
037: * A {@link INameInterceptor} that is used to rewrite names which
038: * reference the <code>container</code> databinding context. This
039: * INameInterceptor is for use by tags that render form-updatable HTML
040: * elements. If the dataSource attribute of the tag references a
041: * <code>container</code> binding context, the name must be qualified
042: * into a real path down a bean / property hierarchy in order to
043: * correctly update that value on a POST. This INameInterceptor
044: * rewrites that name using the given name and the hierarchy of
045: * {@link org.apache.beehive.netui.script.common.IDataAccessProvider} tags in a JSP page.
046: */
047: public class IndexedNameInterceptor implements INameInterceptor {
048: private static final Logger _logger = Logger
049: .getInstance(IndexedNameInterceptor.class);
050:
051: /**
052: * Rewrite an expression into a fully-qualified reference to a specific JavaBean property
053: * on an object.
054: * @param name the expression to rewrite
055: * @param currentTag the current JSP tag that can be used as the leaf for walking up
056: * to find parent tags that provide information used to
057: * rewrite the expression.
058: */
059: public final String rewriteName(String name, Tag currentTag)
060: throws ExpressionEvaluationException {
061: if (_logger.isDebugEnabled())
062: _logger.debug("rewrite expression \"" + name + "\"");
063:
064: IDataAccessProvider dap = getCurrentProvider(currentTag);
065: // if the DAP is null, there is no rewriting to do
066: if (dap == null)
067: return name;
068:
069: // given a hierarchy of "container.container.container.item.someProp", the correct parent needs
070: // to be found so that expression rewriting can happen correctly.
071: //
072: // ensure that this expression contains container.item
073: Expression parsed = getExpressionEvaluator().parseExpression(
074: name);
075: assert parsed != null;
076:
077: int containerCount = 0;
078: List tokens = parsed.getTokens();
079: for (int i = 0; i < tokens.size(); i++) {
080: String tok = tokens.get(i).toString();
081: if (i == 0) {
082: if (!tok.equals("container"))
083: break;
084: else
085: continue;
086: }
087: // this skips the "current" IDataAccessProvider
088: else if (tok.equals("container"))
089: containerCount++;
090: else if (tok.equals("item"))
091: break;
092: }
093:
094: if (_logger.isDebugEnabled())
095: _logger.debug("container parent count: " + containerCount);
096:
097: // now walk up the DataAccessProvier hierarchy until the top-most parent is found
098: // the top-most parent is the first one that does not reference "container.item" but
099: // is bound directly to a specific object such as "actionForm" or "pageFlow". This
100: // handles the case where a set of nested IDataAccessProvider tags are "skipped" by
101: // an expression like "container.container.container.item.foo". In order to find
102: // the correct root to start rewriting the names, one needs to walk up three
103: // DAPs in order to find the correct root from which to start.
104: //
105: // In general, containerCount is zero here for the "container.item.foo" case.
106: for (int i = 0; i < containerCount; i++) {
107: dap = dap.getProviderParent();
108: }
109:
110: // now, the top-most DAP parent is known
111: assert dap != null;
112:
113: // strip off the "container.item" from the expression that is being rewritten
114: // this should be two tokens into the expression.
115: if (containerCount > 0) {
116: name = parsed.getExpression(containerCount);
117: }
118:
119: // now, change the binding context of the parent DAP hierarchy to create a
120: // String that looks like "actionForm.customers[42].order[12].lineItem[2].name"
121: // note, this is done without using the expression that was passed-in and
122: // is derived entirely from the IDataAccessProvider parent hierarchy.
123: String parentNames = rewriteNameInternal(dap);
124:
125: if (_logger.isDebugEnabled())
126: _logger.debug("name hierarchy: " + parentNames + " name: "
127: + name);
128:
129: // with a newly re-written expression prefix, substitute this fully-qualified binding
130: // string into the given expression for "container.item".
131: String newName = changeContext(name, "container.item",
132: parentNames, dap.getCurrentIndex());
133:
134: if (_logger.isDebugEnabled())
135: _logger.debug("rewrittenName: " + newName);
136:
137: return newName;
138: }
139:
140: /**
141: * A default method to find the "current" IDataAccessProvider. This method is
142: * left as non-final so that the implementation here can be tested
143: * outside of a servlet container.
144: */
145: protected IDataAccessProvider getCurrentProvider(Tag tag) {
146: return (IDataAccessProvider) SimpleTagSupport
147: .findAncestorWithClass(tag, IDataAccessProvider.class);
148: }
149:
150: /**
151: * Rewrite a parent IDataAccessProvider's dataSource to be fully qualified.
152: *
153: * "container.container.container.container.item.foo" -> "DS1.DS2.DS3.DS4.foo"
154: */
155: private final String rewriteNameInternal(IDataAccessProvider dap)
156: throws ExpressionEvaluationException {
157: if (_logger.isDebugEnabled())
158: _logger.debug("assign index to name: "
159: + dap.getDataSource());
160:
161: Expression parsedDataSource = getExpressionEvaluator()
162: .parseExpression(dap.getDataSource());
163: assert parsedDataSource != null;
164:
165: // @todo: perf
166: boolean isContainerBound = (parsedDataSource.getTokens().get(0))
167: .toString().equals("container");
168:
169: // rewrite the name of the current IDataAccessProvider.
170: String parentName = null;
171: // if the current DAP has a parent IDataAccessProvider, rewrite the name of the parent
172: if (dap.getProviderParent() != null)
173: parentName = rewriteNameInternal(dap.getProviderParent());
174: // if the current DAP has no parent, or it does not reference the "container." binding context,
175: // we've found the "root" IDataAccessProvider
176: else if (dap.getProviderParent() == null
177: || (dap.getProviderParent() != null && !isContainerBound)) {
178: return dap.getDataSource();
179: }
180:
181: // now, we've found the root and can start rewriting the expressions throughout
182: // the rest of the DAP hierarchy
183: if (_logger.isDebugEnabled()) {
184: _logger.debug("changeContext: DAP.dataSource="
185: + dap.getDataSource()
186: + " oldContext=container newContext=" + parentName
187: + " currentIndex="
188: + dap.getProviderParent().getCurrentIndex()
189: + " parentName is container: " + isContainerBound);
190: }
191:
192: String retVal = null;
193: String ds = dap.getDataSource();
194:
195: // If the current DAP's dataSource is "container.item", the binding context needs to change to that
196: // of the parent. This case should only occur for the last token -- the "name" passed into
197: // the method. Oterwise, just replace the "container" to that of the parent. Both are
198: // qualified with the DAP's current index so that "actionForm.customers" becomes
199: // "actionForm.customers[12]".
200:
201: boolean isContainerItemBound = false;
202: if (isContainerBound
203: && (parsedDataSource.getTokens().get(1)).toString()
204: .equals("item"))
205: isContainerItemBound = true;
206:
207: if (isContainerItemBound)
208: retVal = changeContext(ds, "container.item", parentName,
209: dap.getProviderParent().getCurrentIndex());
210: else
211: retVal = changeContext(ds, "container", parentName, dap
212: .getProviderParent().getCurrentIndex());
213:
214: if (_logger.isDebugEnabled())
215: _logger.debug("fully-qualified binding expression: \""
216: + retVal + "\"");
217:
218: return retVal;
219: }
220:
221: protected ExpressionEvaluator getExpressionEvaluator() {
222: return ExpressionEvaluatorFactory.getInstance();
223: }
224:
225: private final String changeContext(String dataSource,
226: String oldContext, String newContext, int index)
227: throws ExpressionEvaluationException {
228: try {
229: return getExpressionEvaluator().changeContext(dataSource,
230: oldContext, newContext, index);
231: } catch (ExpressionEvaluationException ee) {
232: if (_logger.isErrorEnabled())
233: _logger.error(
234: "An error occurred changing the binding context of the expression \""
235: + dataSource + "\". Cause: " + ee, ee);
236:
237: throw ee;
238: }
239: }
240: }
|