JdbcMapper/beehive-netui-tags/src/main/java/org/apache/beehive/netui/tags/databinding/repeater/Repeater.java

759 lines
30 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.databinding.repeater;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.*;
import java.util.Iterator;
import java.util.List;
import java.util.Collections;
import org.apache.beehive.netui.script.common.DataAccessProviderStack;
import org.apache.beehive.netui.script.common.IDataAccessProvider;
import org.apache.beehive.netui.tags.AbstractClassicTag;
import org.apache.beehive.netui.tags.ExpressionHandling;
import org.apache.beehive.netui.tags.databinding.repeater.pad.PadContext;
import org.apache.beehive.netui.util.Bundle;
import org.apache.beehive.netui.util.exception.LocalizedUnsupportedOperationException;
import org.apache.beehive.netui.util.internal.InternalStringBuilder;
import org.apache.beehive.netui.util.iterator.IteratorFactory;
import org.apache.beehive.netui.util.logging.Logger;
/**
* The <netui-data:repeater> tag is a markup-generic tag that repeats over a data set.
* The repeater tag set is used to render data from a data set into a page. The repeater
* itself does not render any markup. Instead, the markup from its contained tags is
* rendered to create the content generated by this tag. The tags in the repeater tag
* set are as follows:
* <table border="1" cellspacing="0" cellpadding="5" width="75%">
* <tr><td><b>Tag</b></td><td><b>Description</b></td></tr>
* <tr><td>{@link RepeaterHeader}</td><td>Renders once in the {@link #HEADER} state.</td></tr>
* <tr><td>{@link RepeaterItem}</td><td>Renders once in the {@link #ITEM} state.</td></tr>
* <tr><td>{@link RepeaterFooter}</td><td>Renders once in the {@link #FOOTER} state.</td></tr>
* <tr><td>{@link org.apache.beehive.netui.tags.databinding.repeater.pad.Pad}</td><td>Used to convert irregular data sets into regular data sets through padding or truncating the output.</td></tr>
* </table>
* <p>The repeater can render in two modes; the first mode is a simple mode where the body of
* the repeater is rendered once for each item in the data set. In this case, none of the
* other tags above are present in the repeater body. For example, the following will
* render an unordered HTML list of items that are list items which contain the lastName, firstName
* of the current "customer" in the data set.</p>
* <pre>
* &lt;ul&gt;
* &lt;netui-data:repeater dataSource="pageInput.customers"&gt;
* &lt;li&gt;&lt;netui:span value="${container.item.lastName}, ${container.item.firstName}"/&gt;&lt;/li&gt;
* &lt;/netui-data:repeater&gt;
* &lt;/ul&gt;
* </pre>
* <p>The second mode is a more structured mode
* of rendering where the tags above are used to delineate iteration boundaries on the body of
* a &lt;netui-data:repeater> tag. In this case, if one of the above tags is present,
* any content directly in the body of the repeater is not rendered; rather, the content
* inside the structured tags of the repeater is rendered.</p>
* <p>For example, the following will render the same output as the example
* shown above, but it uses the structured tags for rendering the
* <code>pageFlow.customers</code> expression:</p>
* <pre>
* &lt;netui-data:repeater dataSource="pageInput.customers"&gt;
* &lt;netui-data:repeaterHeader&gt;
* &lt;ul&gt;
* &lt;/netui-data:repeaterHeader&gt;
* &lt;netui-data:repeaterItem&gt;
* &lt;li&gt;&lt;netui:span value="${container.item.lastName}, ${container.item.firstName}"/&gt;&lt;/li&gt;
* &lt;/netui-data:repeaterItem&gt;
* &lt;netui-data:repeaterFooter&gt;
* &lt;/ul&gt;
* &lt;/netui-data:repeaterFooter&gt;
* &lt;/netui-data:repeater&gt;
* </pre>
*
* @jsptagref.tagdescription <p>Iterates over a data set to render it as HTML.
* The HTML is specified either directly within the the body of
* &lt;netui-data:repeater> tag or within an associated set of "helper" tags.
* The "helper" tags are listed below.
* <p/>
* <blockquote>
* <table border="1" cellspacing="0" cellpadding="5" width="90%">
* <tr><td><b>Tag</b></td><td><b>Description</b></td></tr>
* <tr><td>{@link RepeaterHeader}</td><td>Renders once at the start of the iteration.</td></tr>
* <tr><td>{@link RepeaterItem}</td><td>Renders once for each iteration.</td></tr>
* <tr><td>{@link RepeaterFooter}</td><td>Renders once at the end of the iteration.</td></tr>
* <tr><td>{@link org.apache.beehive.netui.tags.databinding.repeater.pad.Pad}</td><td>Used to convert
* irregular data sets into regular data sets through padding
* or truncating the output.</td></tr>
* </table>
* </blockquote>
* <p/>
* <p>The &lt;netui-data:repeater> tag can render in two modes; the first mode is a simple mode where the body of
* the &lt;netui-data:repeater> tag is rendered once for each item in the data set. In this case, none of the
* other tags above are present in the repeater body. For example, the following will
* render the items in the "customers" data set as an unordered HTML list.</p>
* <p/>
* <pre> &lt;ul&gt;
* &lt;netui-data:repeater dataSource="pageInput.customers"&gt;
* &lt;li&gt;&lt;netui:span value="${container.item.lastName}, ${container.item.firstName}"/&gt;&lt;/li&gt;
* &lt;/netui-data:repeater&gt;
* &lt;/ul&gt;</pre>
* <p/>
* <p>The second mode is a more structured mode
* of rendering where the "helper" tags
* are used to define the rendering of the data set. In this case, if one of the helper tags
* is present,
* any HTML markup directly in the body of the &lt;netui-data:repeater> tag is not rendered; rather, the HTML markup
* inside the helper tags is rendered.</p>
* <p>For example, the following will render the same output as the example
* shown above, but it uses the "helper" tags for rendering the
* HTML markup:</p>
* <p/>
* <pre>
* &lt;netui-data:repeater dataSource="pageInput.customers"&gt;
* &lt;netui-data:repeaterHeader&gt;
* &lt;ul&gt;
* &lt;/netui-data:repeaterHeader&gt;
* &lt;netui-data:repeaterItem&gt;
* &lt;li&gt;&lt;netui:span value="${container.item.lastName}, ${container.item.firstName}"/&gt;&lt;/li&gt;
* &lt;/netui-data:repeaterItem&gt;
* &lt;netui-data:repeaterFooter&gt;
* &lt;/ul&gt;
* &lt;/netui-data:repeaterFooter&gt;
* &lt;/netui-data:repeater&gt;</pre>
* @example The following sample renders the data set as an HTML table. The table has two columns, "index" and "name",
* and each iteration over the data set is rendered as a row of the table.
* <p/>
* <pre> &lt;netui-data:repeater dataSource="pageInput.myDataSet">
* &lt;netui-data:repeaterHeader>
* &lt;table border="1">
* &lt;tr>
* &lt;td>&lt;b>index&lt;/b>&lt;/td>
* &lt;td>&lt;b>name&lt;/b>&lt;/td>
* &lt;/tr>
* &lt;/netui-data:repeaterHeader>
* &lt;netui-data:repeaterItem>
* &lt;tr>
* &lt;td>
* &lt;netui:span value="${container.index}" />
* &lt;/td>
* &lt;td>
* &lt;netui: value="${container.item}" />
* &lt;/td>
* &lt;/tr>
* &lt;/netui-data:repeaterItem>
* &lt;netui-data:repeaterFooter>
* &lt;/table>
* &lt;/netui-data:repeaterFooter>
* &lt;/netui-data:repeater></pre>
* @netui:tag name="repeater" description="A markup-generic tag that repeats over a data set, and renders the data onto the page."
*/
public class Repeater
extends AbstractClassicTag
implements IDataAccessProvider, TryCatchFinally {
private static final Logger LOGGER = Logger.getInstance(Repeater.class);
/**
* A Repeater rendering state that signals the beginning of repeater rendering.
*/
public static final int INIT = 0;
/**
* A Repeater rendering state that signals the rendering of the HEADER.
* The body renders in the HEADER state once.
*/
public static final int HEADER = 1;
/**
* A Repeater rendering state that signals the rendering of the ITEM.
* The body renders in the ITEM state once for each item in the
* data set.
*/
public static final int ITEM = 2;
/**
* A Repeater rendering state that signals the rendering of the FOOTER.
* The body renders in the FOOTER state once.
*/
public static final int FOOTER = 3;
/**
* A Repeater rendering state that signals the end of repeater rendering.
*/
public static final int END = 4;
private boolean _ignoreNulls = false;
private boolean _haveKids = false;
private boolean _containerInPageContext = false;
private int _currentIndex = -1;
private int _renderedItems = 0;
private int _renderState = INIT;
private Object _defaultText = null;
private Object _currentItem = null;
private String _dataSource = null;
private Iterator _iterator = null;
private PadContext _padContext = null;
private InternalStringBuilder _contentBuffer = null;
/**
* Get the name of this tag. This is used to identify the type of this tag
* for reporting tag errors.
*
* @return a constant String representing the name of this tag.
*/
public String getTagName() {
return "Repeater";
}
/**
* Set a boolean that describes whether the repeater should ignore null
* items encountered while iterating over a data set.
*
* @param ignoreNulls whether or not to ignore nulls
* @jsptagref.attributedescription
* Boolean. If set to true, any null iteration items in the data set will be ignored.
* @jsptagref.attributesyntaxvalue <i>boolean_ignoreNulls</i>
* @netui:attribute required="false"
*/
public void setIgnoreNulls(boolean ignoreNulls) {
_ignoreNulls = ignoreNulls;
}
/**
* @param padContext
*/
public void setPadContext(PadContext padContext) {
if(_padContext == null)
_padContext = padContext;
LOGGER.debug("Repeater has a padContext with text: " + _padContext);
return;
}
/**
* Set the text that will be rendered if the dataSource expression
* references a null object and the defaultText attribute is non-null.
*
* @param defaultText the default text
* @jsptagref.attributedescription
* The text to render if the <code>dataSource</code> attribute references a null data set.
* @jsptagref.attributesyntaxvalue <i>string_defaultText</i>
* @netui:attribute required="false" rtexprvalue="true"
*/
public void setDefaultText(Object defaultText) {
_defaultText = defaultText;
}
/**
* Get the index of the current iteration through the body of this tag. This
* data can be accessed using the expression <code>container.index</code>
* on an attribute of a databindable NetUI tag that is contained within the
* repeating body of this tag. This expression is only valid when the dataset
* is being rendered.
*
* @return the integer index of the current data item in the data set
* @see org.apache.beehive.netui.script.common.IDataAccessProvider
*/
public int getCurrentIndex() {
return _currentIndex;
}
/**
* Get the item that is currently being rendered by this repeating tag.
* This can be accessed using the expression <code>expression.item</code>
* on an attribute of a databindable NetUI tag that is contained within
* the repeating body of this tag. The expression is only valid when the dataset
* is being rendered.
*
* @return the current item in the data set
* @see org.apache.beehive.netui.script.common.IDataAccessProvider
*/
public Object getCurrentItem() {
return _currentItem;
}
/**
* Get the metadata for the current item. This method is not supported by
* this tag.
*
* @throws UnsupportedOperationException this tag does not support this method from the IDataAccessProvider interface
* @see org.apache.beehive.netui.script.common.IDataAccessProvider
*/
public Object getCurrentMetadata() {
LocalizedUnsupportedOperationException uoe =
new LocalizedUnsupportedOperationException("The " + getTagName() + "does not export metadata for its iterated items.");
uoe.setLocalizedMessage(Bundle.getErrorString("Tags_DataAccessProvider_metadataUnsupported", new Object[]{getTagName()}));
throw uoe;
}
/**
* Get the parent IDataAccessProvider for this tag. If this tag is contained within
* a IDataAccessProvider, the containing IDataAccessProvider is available through the
* expression <code>container.container</code>. Any valid properties of the
* parent IDataAccessProvider can be accessed through this expression. This method
* will return null if there is no parent IDataAccessProvider
*
* @return a containing IDataAccessProvider if one exists, null otherwise.
* @see org.apache.beehive.netui.script.common.IDataAccessProvider
*/
public IDataAccessProvider getProviderParent() {
IDataAccessProvider dap = (IDataAccessProvider)SimpleTagSupport.findAncestorWithClass(this, IDataAccessProvider.class);
return dap;
}
/**
* Get the current render state for the repeater. This tag is used by child tags
* to access the current location in the repeater's rendering lifecycle.
*
* @return an integer that represents the current state of the grid; this is one
* of {@link #INIT}, {@link #HEADER}, {@link #ITEM},{@link #FOOTER}, or {@link #END}.
*/
public int getRenderState() {
return _renderState;
}
/**
* Add content to the content that is being buffered by this tag. All content
* written by the body of this tag is added to this buffer. The buffer is rendered
* at the end of the tag's lifecycle if no fatal errors have occurred during this
* tag's lifecycle.
*
* @param content content that this tag should render.
*/
public void addContent(String content) {
if(_contentBuffer == null) {
int size = (content != null ? (5 * content.length()) : 1024);
_contentBuffer = new InternalStringBuilder(size);
}
_contentBuffer.append(content);
}
/**
* Method used by tags in the repeater tag set to register the presence of a contained
* tag. When registered, the repeater will change the way in which it renders to
* either use structured or non-structured rendering.
* @param repeaterComponent {@link RepeaterComponent} to register with the Repeater parent
*/
public void registerChildTag(RepeaterComponent repeaterComponent) {
_haveKids = true;
}
/**
* Start rendering the repeater.
* @return {@link #SKIP_BODY} if an error occurs; {@link #EVAL_BODY_BUFFERED} otherwise
* @throws JspException if an error occurs that can not be reported in the page
*/
public int doStartTag()
throws JspException {
Object source = evaluateDataSource();
// report any errors that may have occured
if(hasErrors())
return SKIP_BODY;
_renderState = INIT;
boolean empty = false;
if(source != null) {
_iterator = IteratorFactory.createIterator(source);
if(_iterator == null) {
LOGGER.warn(Bundle.getString("Tags_Repeater_nullIterator"));
_iterator = Collections.EMPTY_LIST.iterator();
}
if(_iterator.hasNext()) {
_currentIndex = 0;
_currentItem = _iterator.next();
if(_ignoreNulls && _currentItem == null) {
/*
doStartTag doesn't know if the repeater is structured or unstructured
thus, if ignoreNulls is true, it's going to make an attempt to go
through the body with a non-null item. if there are no non-null
items in the data structure, the doAfterBody method will handle
this correctly, but a data structure with a null first item
will render the same as a data structure with a null second item
*/
advanceToNonNullItem();
/*
this null check needs to re-run here because the advanceToNonNullItem method
side-effects the _currentItem.
*/
if(_currentItem == null)
empty = true;
}
}
/* there is no data set of there are zero items in the iterator */
else empty = true;
}
/* the dataSource evaluated to null */
else {
_iterator = Collections.EMPTY_LIST.iterator();
empty = true;
}
if(empty) {
/* if the defaultText attribute is non-null, it will be evaluated as an expression and rendered to the page */
if(_defaultText != null)
addContent(_defaultText.toString());
return SKIP_BODY;
}
else {
DataAccessProviderStack.addDataAccessProvider(this, pageContext);
_containerInPageContext = true;
return EVAL_BODY_BUFFERED;
}
}
/**
* <p>
* Continue rendering the repeater changing the render state or advancing to a new data item
* as needed.
* </p>
* @return {@link #SKIP_BODY} if an error occurs or the data set has been rendered; {@link #EVAL_BODY_AGAIN} otherwise
*/
public int doAfterBody() {
if(hasErrors())
return SKIP_BODY;
LOGGER.debug("structured repeater: " + _haveKids + " render state: " + renderStateToString(_renderState));
/*
structured rendering of the repeater; body content is ignored and real
content is rendered through cooperating nested tags
*/
if(_haveKids)
return renderStructured();
/*
unstructured rendering of the repeater; this means that there isn't
a repeater(Header|Item|Footer) inside the body of the repeater.
*/
else {
if(bodyContent != null) {
addContent(bodyContent.getString());
bodyContent.clearBody();
}
if(_iterator.hasNext()) {
_currentIndex++;
_currentItem = _iterator.next();
if(_ignoreNulls && _currentItem == null) {
advanceToNonNullItem();
/* ignoring null items and no more non-null items, so skip to doEndTag() */
if(_currentItem == null)
return SKIP_BODY;
}
/* found another item; re-render the repeater's body */
return EVAL_BODY_AGAIN;
}
/* no more items; skip to doEndTag() */
else return SKIP_BODY;
}
}
/**
* Complete rendering the repeater.
* @return {@link #EVAL_PAGE}
* @throws JspException if an error occurs that can not be reported in the page
*/
public int doEndTag()
throws JspException {
if(hasErrors())
reportErrors();
else if(_contentBuffer != null)
write(_contentBuffer.toString());
return EVAL_PAGE;
}
public void doFinally() {
localRelease();
}
public void doCatch(Throwable t)
throws Throwable {
throw t;
}
/**
* Reset all of the fields of this tag.
*/
protected void localRelease() {
super.localRelease();
_currentItem = null;
_currentIndex = -1;
_iterator = null;
_defaultText = null;
_renderState = INIT;
_haveKids = false;
_contentBuffer = null;
_padContext = null;
_ignoreNulls = false;
_renderedItems = 0;
if(_containerInPageContext) {
DataAccessProviderStack.removeDataAccessProvider(pageContext);
_containerInPageContext = false;
}
}
/**
* Render a the repeater using the full repeater lifecycle. This
* method is executed after each pass through the body if there
* are tags from the repeater tag set in the body of this repeater.
* This method ensures that the repeater tag runs as a full state
* machine for these tags.
*
* @return EVAL_BODY_AGAIN unless the lifecycle has completed; then return SKIP_BODY
*/
/* todo: perf -- optimize the number of trips through the body by ignoring the header / footer when necessary */
private int renderStructured() {
if(LOGGER.isDebugEnabled() && _padContext != null)
LOGGER.debug("\ncurrentIndex: " + _currentIndex + "\n" +
"checkMaxRepeat: " + _padContext.checkMaxRepeat(_currentIndex) + "\n" +
"checkMinRepeat: " + _padContext.checkMinRepeat(_currentIndex) + "\n");
if(_renderState == INIT) {
_renderState = HEADER;
return EVAL_BODY_AGAIN;
}
if(_renderState == HEADER) {
assert _renderedItems == 0;
/* this would only happen if Pad.maxRepeat == 0 */
if(_padContext != null && _padContext.checkMaxRepeat(_renderedItems)) {
_renderState = FOOTER;
return EVAL_BODY_AGAIN;
}
if(_currentItem == null && _ignoreNulls) {
advanceToNonNullItem();
/* no non-null item was found; render the footer */
if(_currentItem == null) {
doPadding();
// render the header
_renderState = FOOTER;
}
/* non-null item found; it's not the 0th item; render it */
else _renderState = ITEM;
}
/* 0th item is non-null; render it */
else _renderState = ITEM;
return EVAL_BODY_AGAIN;
}
if(_renderState == ITEM) {
_renderedItems++;
/* check that the maximum number of items to render has *not* been reached */
if(_iterator.hasNext() && (_padContext == null || (_padContext != null && !_padContext.checkMaxRepeat(_renderedItems)))) {
_currentIndex++;
_currentItem = _iterator.next();
if(_ignoreNulls && _currentItem == null) {
advanceToNonNullItem();
/* last item */
if(_currentItem == null) {
doPadding();
/* render the header */
_renderState = FOOTER;
return EVAL_BODY_AGAIN;
}
}
/* if _ignoreNulls is false, the _currentItem may be null here */
return EVAL_BODY_AGAIN;
}
/*
have finished rendering items for some reason:
1) there isn't a next item
2) reached the maximum number of items to render
So:
1) pad if necessary
2) render the footer
*/
else {
doPadding();
_renderState = FOOTER;
return EVAL_BODY_AGAIN;
}
}
if(_renderState == FOOTER) {
_renderState = END;
return SKIP_BODY;
}
return SKIP_BODY;
}
/**
* <p/>
* Internal utility method.
* </p>
* </p>
* This is called in places where the repeater needs to move to the next
* non-null item in the data set to render. This occurs when the
* current data item is null and the <code>ignoreNulls</code>
* flag has been set to skip rendering null items in the data set.
* </p>
* <p/>
* This method side-effects to advance the iterator to the next
* non-null item or the end if there are zero remaining non-null
* items.
* </p>
* <p/>
* At the end, the <code>currentItem</code> may be null, and the
* <code>currentIndex</code> will reference either the integer
* location in the data structure of the non-null data item, or
* it will reference the end of the data structure.
* </p>
*/
private final void advanceToNonNullItem() {
assert _iterator != null;
assert _currentItem == null;
while(_iterator.hasNext() && _currentItem == null) {
_currentItem = _iterator.next();
_currentIndex++;
}
}
/**
* When using the repeater's pad tag, it is possible to require a minimum number of
* items render in the repeater. This method pads out the number of items until it
* reaches the {@link org.apache.beehive.netui.tags.databinding.repeater.pad.PadContext}'s
* <code>minRepeat</code> property.
*/
private final void doPadding() {
if(_padContext != null && !_padContext.checkMinRepeat(_renderedItems)) {
/*
since padding is now running, un-set the current item so that the last
item isn't accessible during any later data binding
*/
_currentItem = null;
for(int i = _renderedItems; !_padContext.checkMinRepeat(i); i++) {
_currentIndex++;
addContent(_padContext.getPadText());
}
}
}
/**
* An internal method that turns the current render state into a string for debugging.
*/
private static final String renderStateToString(int state) {
switch(state) {
case INIT:
return "INIT";
case HEADER:
return "HEADER";
case ITEM:
return "ITEM";
case FOOTER:
return "FOOTER";
case END:
return "END";
default:
return "INVALID STATE";
}
}
/**
* Return an <code>ArrayList</code> which represents a chain of <code>INameInterceptor</code>
* objects. This method by default returns <code>null</code> and should be overridden
* by objects that support naming.
* @return an <code>ArrayList</code> that will contain <code>INameInterceptor</code> objects.
*/
protected List getNamingChain() {
return AbstractClassicTag.DefaultNamingChain;
}
/**
* Sets the tag's data source (can be an expression).
* @param dataSource - the data source
* @jsptagref.attributedescription <p>The <code>dataSource</code> attribute determines both
* (1) the source of populating data for the tag and
* (2) the object to which the tag submits data.
*
* <p>For example, assume that the Controller file (= JPF file) contains
* a Form Bean with the property foo. Then the following &lt;netui:textBox> tag will
* (1) draw populating data from the Form Bean's foo property and (2)
* submit user defined data to the same property.
*
* <p>&nbsp;&nbsp;&nbsp;&nbsp;<code>&lt;netui:textBox dataSource="actionForm.foo" /></code>
*
* <p>The <code>dataSource</code> attribute takes either a data binding expression or
* the name of a Form Bean property. In the
* above example, <code>&lt;netui:textBox dataSource="foo" /></code>
* would have the exactly same behavior.
*
* <p>When the tag is used to submit data, the data binding expression must
* refer to a Form Bean property.
* In cases where the tag is not used to submit data, but is used for
* displaying data only, the data
* binding expression need not refer to a Form Bean property. For example,
* assume that myIterativeData is a member variable on
* the Controller file ( = JPF file). The following &lt;netui-data:repeater>
* tag draws its data from myIterativeData.
*
* <p>&nbsp;&nbsp;&nbsp;&nbsp;<code>&lt;netui-data:repeater dataSource="pageFlow.myIterativeData"></code>
* @jsptagref.attributesyntaxvalue <i>expression_datasource</i>
* @netui:attribute required="true" rtexprvalue="true"
*/
public void setDataSource(String dataSource) {
_dataSource = dataSource;
}
/**
* Gets the tag's data source (can be an expression).
* @return the data source
*/
public String getDataSource() {
return "{" + _dataSource + "}";
}
/**
* Return the Object that is represented by the specified data source.
* @return Object
* @throws JspException
*/
private Object evaluateDataSource()
throws JspException {
ExpressionHandling expr = new ExpressionHandling(this);
String dataSource = getDataSource();
String ds = expr.ensureValidExpression(dataSource, "dataSource", "DataSourceError");
if (ds == null)
return null;
/* have a valid expression */
return expr.evaluateExpression(dataSource, "dataSource", pageContext);
}
}