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

481 lines
18 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;
import org.apache.beehive.netui.util.internal.InternalStringBuilder;
import org.apache.beehive.netui.core.urls.URLRewriterService;
import org.apache.beehive.netui.script.ExpressionEvaluationException;
import org.apache.beehive.netui.tags.javascript.IScriptReporter;
import org.apache.beehive.netui.tags.javascript.ScriptContainer;
import org.apache.beehive.netui.tags.naming.FormDataNameInterceptor;
import org.apache.beehive.netui.tags.naming.INameInterceptor;
import org.apache.beehive.netui.tags.naming.IndexedNameInterceptor;
import org.apache.beehive.netui.tags.html.Form;
import org.apache.beehive.netui.util.Bundle;
import org.apache.beehive.netui.util.logging.Logger;
import org.apache.beehive.netui.pageflow.internal.InternalUtils;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import javax.servlet.jsp.tagext.Tag;
import java.io.IOException;
import java.util.*;
/**
* AbstractBaseTag is the base tag for most of the NetUI tags. This tag provides default behavior
* and services for NetUI tags. There following categories of services are provided by this tag:
* <ul>
* <li><b>Generic Services</b> -- These are general services such as access to local, writting
* to the response, and writting attributes.</li>
* <li><b>Expression Management</b> -- This set of method provide evaluation and information about
* expressions. These methods allow tags to fully support expressions for attributes.</li>
* <li><b>Naming and NamingInterceptor Services</b> -- This set of methods will apply
* <code>INameInterceptor</code>s to a name to produce the name written out. In addition, it allows
* the URL Rewritter service to modify names.</li>
* <li><b>Attribute Rendering Support</b> -- This set of routine allow tags to keep simple attributes
* in a map that can be written into the generated markup. There are two types of attributes, attributes
* that contain expression and attributes that do not contain expression supported.</li>
* <li><b>Error Reporting</b> -- This set of routines will report errors within the tags. In development
* mode errors are reported in-page.</li>
* <li><b>JavaScript Services</b> -- This set of routines provide simple access for JavaScript generation.</li>
* </ul>
* @netui:tag
*/
public abstract class AbstractClassicTag
extends BodyTagSupport implements INetuiTag
{
//@todo: need to implement the flag to turn errors into JSP exceptions
private static final Logger logger = Logger.getInstance(AbstractClassicTag.class);
/**
* This List represents the default naming chain for handling <code>dataSource</code> attributes. The list
* is a read-only list which may be used by any <code>dataSource</code> implementation.
*/
public static final List DefaultNamingChain;
/**
* This is the name of a request scoped attribute which creates a unique id for processing
* a request.
*/
public static final String NETUI_UNIQUE_CNT = "netui.unique.id";
/**
* This is a private formRewriter which is used by <code>qualifyName</code> to provide
* struts naming behavior.
*/
private static final INameInterceptor formRewriter = new FormDataNameInterceptor();
/**
* String constant for the empty string.
*/
protected static final String EMPTY_STRING = "";
// create the default naming chain.
static
{
List l = new ArrayList(2);
l.add(new FormDataNameInterceptor());
l.add(new IndexedNameInterceptor());
DefaultNamingChain = Collections.unmodifiableList(l);
}
private ErrorHandling _eh; // This class will track and handle errors
/////////////////////////// Generic Services support ////////////////////////////
/**
* Return the name of the tag. Used by error reporting to get the name of the tag.
* @return the name of the tag.
*/
public abstract String getTagName();
/**
* This is a method that will reinitialize all temporary state on a
* tag and should be called in the doEndTag method.
*/
protected void localRelease()
{
_eh = null;
}
/**
* This method will return the user local of the request.
* @return the Locale object to use when rendering this tag
*/
protected Locale getUserLocale()
{
return InternalUtils.lookupLocale(pageContext.getRequest());
}
/**
* This mehod will write the passed string to the response.
* @param string to be written to the response.
*/
protected final void write(String string)
{
JspWriter writer = pageContext.getOut();
try {
writer.print(string);
}
catch (IOException e) {
logger.error(Bundle.getString("Tags_WriteException"), e);
org.apache.struts.util.RequestUtils.saveException((PageContext) pageContext, e);
}
}
/////////////////////////// Naming and NamingInterceptor support ////////////////////////////
/**
* Return an <code>List</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>List</code> that will contain <code>INameInterceptor</code> objects.
*/
protected List getNamingChain()
{
return null;
}
/**
* This method walks all of the naming chains and allows them to rewrite the <code>name</code> parameter.
* After the naming chain processes the name, it will be passed to <code>rewriteName</code> for final processing.
* If the naming chaing returned from <code>getNamingChain</code> returns null, the name will be passed to
* <code>rewriteName</code> and returned. If there is an <code>ExpressionEvaluationException</code> thrown
* by a <code>INameInterceptor</code>, the error will be registered with the tag and <code>null</code> will
* be returned.
* @param name the name to rewrite
* @return the name after it was passed to all <code>INameInterceptor</code>s in the naming chain.
* @see #rewriteName
* @see org.apache.beehive.netui.tags.naming.INameInterceptor
*/
protected String applyNamingChain(String name)
throws JspException
{
assert (name != null) : "The name parameter may not be null";
List namingChain = getNamingChain();
if (namingChain == null)
return rewriteName(name);
//if (logger.isDebugEnabled())
// logger.debug("rewrite name \"" + name + "\" on tag of type \"" + getClass().getName() + " with namingChain " +
// (namingChain != null ? "size " + namingChain.size() : "null"));
try {
String newName = name;
int cnt = namingChain.size();
for (int i = 0; i < cnt; i++) {
//if (logger.isDebugEnabled())
// logger.debug("rewriteName: \"" + newName + "\" with INameInterceptor: " + namingChain.get(i).getClass().getName());
newName = ((INameInterceptor) namingChain.get(i)).rewriteName(newName, this);
//if (logger.isDebugEnabled())
// logger.debug("rewrite result: " + newName);
}
return rewriteName(newName);
}
catch (ExpressionEvaluationException ee) {
// if there is an expression evaluation error set the error and return null;
logger.error(Bundle.getString("Tags_ExpressionQualifyingFailure", name));
// create the expression info an add it to the error tracking
EvalErrorInfo info = new EvalErrorInfo();
info.evalExcp = ee;
info.expression = name;
info.attr = "dataSource";
info.tagType = getTagName();
// report the error
registerTagError(info);
return null;
}
}
/**
* An internal method that allows a tag to qualify the <code>name</code> paramater by converting
* it from a struts style naming convention to an explicit databinding expression. The qualified
* name will be returned. This method may report an error if there is an error in the expression.
* @param name the name to be qualified
* @return the name which has been qualified
* @throws JspException throws a JspException if in-page error reporting is turned off.
* @see org.apache.beehive.netui.tags.naming.FormDataNameInterceptor
*/
protected String qualifyAttribute(String name)
throws JspException
{
if (name == null)
return null;
// if this is a Struts style name, convert it to an expression
try {
name = formRewriter.rewriteName(name, this);
}
catch (ExpressionEvaluationException e) {
String s = Bundle.getString("Tags_DataSourceExpressionError", new Object[]{name, e.toString()});
registerTagError(s, null);
}
return name;
}
/**
* This method will rewrite the name (id) by passing it to the
* URL Rewritter and getting back a value.
* @param name the name that will be rewritten
* @return a name that has been rewritten by the URLRewriterService.
*/
final protected String rewriteName(String name)
{
return URLRewriterService.getNamePrefix(pageContext.getServletContext(), pageContext.getRequest(), name) + name;
}
/**
* This method will generate a real id based upon the passed in tagId. The generated
* id will be constucted by searching upward for all the script containers that have a
* scope id set. These will form a fully qualified id.
* @param tagId The base tagId set on a tag
* @return an id value formed by considering all of the scope id's found in the tag hierarchy.
*/
final protected String getIdForTagId(String tagId)
{
HttpServletRequest req = (HttpServletRequest) pageContext.getRequest();
ArrayList/*<String>*/ list = (ArrayList/*<String>*/)
RequestUtils.getOuterAttribute(req, ScriptContainer.SCOPE_ID);
if (list == null)
return tagId;
InternalStringBuilder sb = new InternalStringBuilder();
for (int i=0;i<list.size();i++) {
sb.append((String)list.get(i));
sb.append('.');
}
sb.append(tagId);
return sb.toString();
/*
Tag tag = this;
while (tag != null) {
if (tag instanceof ScriptContainer) {
String sid = ((ScriptContainer) tag).getIdScope();
if (sid != null) {
tagId = sid + "." + tagId;
}
}
tag = tag.getParent();
}
return tagId;
*/
}
/////////////////////////// Generic Attribute Setting Support ////////////////////////////
/**
* Report an error if the value of <code>attrValue</code> is equal to the empty string, otherwise return
* that value. If <code>attrValue</code> is equal to the empty string, an error is registered and
* null is returned.
* @param attrValue The value to be checked for the empty string
* @param attrName The name of the attribute
* @return either the attrValue if it is not the empty string or null
* @throws JspException A JspException will be thrown if inline error reporting is turned off.
*/
protected final String setRequiredValueAttribute(String attrValue, String attrName)
throws JspException
{
assert(attrValue != null) : "parameter 'attrValue' must not be null";
assert(attrName != null) : "parameter 'attrName' must not be null";
if ("".equals(attrValue)) {
String s = Bundle.getString("Tags_AttrValueRequired", new Object[]{attrName});
registerTagError(s, null);
return null;
}
return attrValue;
}
/**
* Filter out the empty string value and return either the value or null. When the value of
* <code>attrValue</code> is equal to the empty string this will return null, otherwise it will
* return the value of <code>attrValue</code>.
* @param attrValue This is the value we will check for the empty string.
* @return either the value of attrValue or null
*/
protected final String setNonEmptyValueAttribute(String attrValue)
{
return ("".equals(attrValue)) ? null : attrValue;
}
/////////////////////////// Generic Error Reporting Support ////////////////////////////
/**
* This is a simple routine which will call the error reporter if there is an
* error and then call local release before returning the <code>returnValue</code>.
* This is a very common code sequence in the Classic Tags so we provide this routine.
* @param returnValue The value that will be returned.
* @return <code>returnValue</code> is always returned.
* @throws JspException
*/
protected int reportAndExit(int returnValue)
throws JspException
{
if (hasErrors()) {
reportErrors();
}
localRelease();
return returnValue;
}
/**
* This will report an error from a tag. The error will
* contain a message. If error reporting is turned off,
* the message will be returned and the caller should throw
* a JspException to report the error.
* @param message - the message to register with the error
* @throws JspException - if in-page error reporting is turned off this method will always
* throw a JspException.
*/
public void registerTagError(String message, Throwable e)
throws JspException
{
ErrorHandling eh = getErrorHandling();
eh.registerTagError(message, getTagName(), this, e);
}
/**
* This will report an error from a tag. The error must
* be be an AbstractPageError.
* @param error The <code>AbstractPageError</code> to add to the error list.
* @throws JspException - if in-page error reporting is turned off this method will always
* throw a JspException.
*/
public void registerTagError(AbstractPageError error)
throws JspException
{
ErrorHandling eh = getErrorHandling();
eh.registerTagError(error, this);
}
/**
* This method will return <code>true</code> if there have been any errors registered on this
* tag. Otherwise it returns <code>false</code>
* @return <code>true</code> if errors have been reported on this tag.
*/
protected boolean hasErrors()
{
return (_eh != null);
}
/**
* This method will write out the <code>String</code> returned by <code>getErrorsReport</code> to the
* response output stream.
* @throws JspException if <code>write</code> throws an exception.
* @see #write
*/
protected void reportErrors()
throws JspException
{
assert(_eh != null);
String err = _eh.getErrorsReport(getTagName());
IErrorCollector ec = (IErrorCollector) SimpleTagSupport.findAncestorWithClass(this, IErrorCollector.class);
if (ec != null) {
ec.collectChildError(err);
}
else {
write(err);
}
}
/**
* This method will return a <code>String<code> that represents all of the errors that were
* registered for the tag. This method assumes that there are errors in the tag and asserts
* this is true. Code will typically call <code>hasErrors</code> before calling this method.
* @return A <code>String</code> that contains all of the errors registered on this tag.
*/
protected String getErrorsReport()
{
assert _eh != null;
return _eh.getErrorsReport(getTagName());
}
/**
* This method will return an ErrorHandling instance.
* @return Return the ErrorHandling object
*/
private ErrorHandling getErrorHandling()
{
if (_eh == null) {
_eh = new ErrorHandling();
}
return _eh;
}
/////////////////////////// JavaScript Support Support ////////////////////////////
/**
* Return the closest <code>ScriptReporter</code> in the parental chain. Searching starts
* at this node an moves upward through the parental chain.
* @return a <code>ScriptReporter</code> or null if there is not one found.
*/
protected IScriptReporter getScriptReporter()
{
return (IScriptReporter) SimpleTagSupport.findAncestorWithClass(this, IScriptReporter.class);
}
/////////////////////////// Misc Features Support ////////////////////////////
/**
* This method will generate the next unique int within the HTML tag.
* @param req the Request
* @return the next unique integer for this request.
*/
protected int getNextId(ServletRequest req)
{
Integer i = (Integer) RequestUtils.getOuterAttribute((HttpServletRequest) req,NETUI_UNIQUE_CNT);
if (i == null) {
i = new Integer(0);
}
int ret = i.intValue();
RequestUtils.setOuterAttribute((HttpServletRequest) req,NETUI_UNIQUE_CNT, new Integer(ret + 1));
return ret;
}
/**
* Returns the closest parent form tag, or null if there is none.
*/
protected Form getNearestForm()
{
Tag parentTag = getParent();
while (parentTag != null) {
if (parentTag instanceof Form)
return (Form) parentTag;
parentTag = parentTag.getParent();
}
return null;
}
}