You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by mu...@apache.org on 2009/09/28 03:55:35 UTC
svn commit: r819444 [19/27] - in /struts/struts2/trunk/plugins/embeddedjsp:
./ src/main/java/org/apache/struts2/el/
src/main/java/org/apache/struts2/el/lang/
src/main/java/org/apache/struts2/el/parser/
src/main/java/org/apache/struts2/el/util/ src/main...
Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Validator.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Validator.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Validator.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Validator.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,1799 @@
+/*
+ * 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.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+
+import javax.el.ELException;
+import javax.el.ExpressionFactory;
+import javax.el.FunctionMapper;
+import javax.servlet.jsp.tagext.FunctionInfo;
+import javax.servlet.jsp.tagext.JspFragment;
+import javax.servlet.jsp.tagext.PageData;
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagData;
+import javax.servlet.jsp.tagext.TagExtraInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+import javax.servlet.jsp.tagext.ValidationMessage;
+
+import org.apache.struts2.el.lang.ELSupport;
+import org.apache.struts2.jasper.Constants;
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.el.ELContextImpl;
+import org.xml.sax.Attributes;
+
+/**
+ * Performs validation on the page elements. Attributes are checked for
+ * mandatory presence, entry value validity, and consistency. As a side effect,
+ * some page global value (such as those from page direcitves) are stored, for
+ * later use.
+ *
+ * @author Kin-man Chung
+ * @author Jan Luehe
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+class Validator {
+
+ /**
+ * A visitor to validate and extract page directive info
+ */
+ static class DirectiveVisitor extends Node.Visitor {
+
+ private PageInfo pageInfo;
+
+ private ErrorDispatcher err;
+
+ private static final JspUtil.ValidAttribute[] pageDirectiveAttrs = {
+ new JspUtil.ValidAttribute("language"),
+ new JspUtil.ValidAttribute("extends"),
+ new JspUtil.ValidAttribute("import"),
+ new JspUtil.ValidAttribute("session"),
+ new JspUtil.ValidAttribute("buffer"),
+ new JspUtil.ValidAttribute("autoFlush"),
+ new JspUtil.ValidAttribute("isThreadSafe"),
+ new JspUtil.ValidAttribute("info"),
+ new JspUtil.ValidAttribute("errorPage"),
+ new JspUtil.ValidAttribute("isErrorPage"),
+ new JspUtil.ValidAttribute("contentType"),
+ new JspUtil.ValidAttribute("pageEncoding"),
+ new JspUtil.ValidAttribute("isELIgnored"),
+ new JspUtil.ValidAttribute("deferredSyntaxAllowedAsLiteral"),
+ new JspUtil.ValidAttribute("trimDirectiveWhitespaces")
+ };
+
+ private boolean pageEncodingSeen = false;
+
+ /*
+ * Constructor
+ */
+ DirectiveVisitor(Compiler compiler) throws JasperException {
+ this.pageInfo = compiler.getPageInfo();
+ this.err = compiler.getErrorDispatcher();
+ }
+
+ public void visit(Node.IncludeDirective n) throws JasperException {
+ // Since pageDirectiveSeen flag only applies to the Current page
+ // save it here and restore it after the file is included.
+ boolean pageEncodingSeenSave = pageEncodingSeen;
+ pageEncodingSeen = false;
+ visitBody(n);
+ pageEncodingSeen = pageEncodingSeenSave;
+ }
+
+ public void visit(Node.PageDirective n) throws JasperException {
+
+ JspUtil.checkAttributes("Page directive", n, pageDirectiveAttrs,
+ err);
+
+ // JSP.2.10.1
+ Attributes attrs = n.getAttributes();
+ for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
+ String attr = attrs.getQName(i);
+ String value = attrs.getValue(i);
+
+ if ("language".equals(attr)) {
+ if (pageInfo.getLanguage(false) == null) {
+ pageInfo.setLanguage(value, n, err, true);
+ } else if (!pageInfo.getLanguage(false).equals(value)) {
+ err.jspError(n, "jsp.error.page.conflict.language",
+ pageInfo.getLanguage(false), value);
+ }
+ } else if ("extends".equals(attr)) {
+ if (pageInfo.getExtends(false) == null) {
+ pageInfo.setExtends(value, n);
+ } else if (!pageInfo.getExtends(false).equals(value)) {
+ err.jspError(n, "jsp.error.page.conflict.extends",
+ pageInfo.getExtends(false), value);
+ }
+ } else if ("contentType".equals(attr)) {
+ if (pageInfo.getContentType() == null) {
+ pageInfo.setContentType(value);
+ } else if (!pageInfo.getContentType().equals(value)) {
+ err.jspError(n, "jsp.error.page.conflict.contenttype",
+ pageInfo.getContentType(), value);
+ }
+ } else if ("session".equals(attr)) {
+ if (pageInfo.getSession() == null) {
+ pageInfo.setSession(value, n, err);
+ } else if (!pageInfo.getSession().equals(value)) {
+ err.jspError(n, "jsp.error.page.conflict.session",
+ pageInfo.getSession(), value);
+ }
+ } else if ("buffer".equals(attr)) {
+ if (pageInfo.getBufferValue() == null) {
+ pageInfo.setBufferValue(value, n, err);
+ } else if (!pageInfo.getBufferValue().equals(value)) {
+ err.jspError(n, "jsp.error.page.conflict.buffer",
+ pageInfo.getBufferValue(), value);
+ }
+ } else if ("autoFlush".equals(attr)) {
+ if (pageInfo.getAutoFlush() == null) {
+ pageInfo.setAutoFlush(value, n, err);
+ } else if (!pageInfo.getAutoFlush().equals(value)) {
+ err.jspError(n, "jsp.error.page.conflict.autoflush",
+ pageInfo.getAutoFlush(), value);
+ }
+ } else if ("isThreadSafe".equals(attr)) {
+ if (pageInfo.getIsThreadSafe() == null) {
+ pageInfo.setIsThreadSafe(value, n, err);
+ } else if (!pageInfo.getIsThreadSafe().equals(value)) {
+ err.jspError(n, "jsp.error.page.conflict.isthreadsafe",
+ pageInfo.getIsThreadSafe(), value);
+ }
+ } else if ("isELIgnored".equals(attr)) {
+ if (pageInfo.getIsELIgnored() == null) {
+ pageInfo.setIsELIgnored(value, n, err, true);
+ } else if (!pageInfo.getIsELIgnored().equals(value)) {
+ err.jspError(n, "jsp.error.page.conflict.iselignored",
+ pageInfo.getIsELIgnored(), value);
+ }
+ } else if ("isErrorPage".equals(attr)) {
+ if (pageInfo.getIsErrorPage() == null) {
+ pageInfo.setIsErrorPage(value, n, err);
+ } else if (!pageInfo.getIsErrorPage().equals(value)) {
+ err.jspError(n, "jsp.error.page.conflict.iserrorpage",
+ pageInfo.getIsErrorPage(), value);
+ }
+ } else if ("errorPage".equals(attr)) {
+ if (pageInfo.getErrorPage() == null) {
+ pageInfo.setErrorPage(value);
+ } else if (!pageInfo.getErrorPage().equals(value)) {
+ err.jspError(n, "jsp.error.page.conflict.errorpage",
+ pageInfo.getErrorPage(), value);
+ }
+ } else if ("info".equals(attr)) {
+ if (pageInfo.getInfo() == null) {
+ pageInfo.setInfo(value);
+ } else if (!pageInfo.getInfo().equals(value)) {
+ err.jspError(n, "jsp.error.page.conflict.info",
+ pageInfo.getInfo(), value);
+ }
+ } else if ("pageEncoding".equals(attr)) {
+ if (pageEncodingSeen)
+ err.jspError(n, "jsp.error.page.multi.pageencoding");
+ // 'pageEncoding' can occur at most once per file
+ pageEncodingSeen = true;
+ String actual = comparePageEncodings(value, n);
+ n.getRoot().setPageEncoding(actual);
+ } else if ("deferredSyntaxAllowedAsLiteral".equals(attr)) {
+ if (pageInfo.getDeferredSyntaxAllowedAsLiteral() == null) {
+ pageInfo.setDeferredSyntaxAllowedAsLiteral(value, n,
+ err, true);
+ } else if (!pageInfo.getDeferredSyntaxAllowedAsLiteral()
+ .equals(value)) {
+ err
+ .jspError(
+ n,
+ "jsp.error.page.conflict.deferredsyntaxallowedasliteral",
+ pageInfo
+ .getDeferredSyntaxAllowedAsLiteral(),
+ value);
+ }
+ } else if ("trimDirectiveWhitespaces".equals(attr)) {
+ if (pageInfo.getTrimDirectiveWhitespaces() == null) {
+ pageInfo.setTrimDirectiveWhitespaces(value, n, err,
+ true);
+ } else if (!pageInfo.getTrimDirectiveWhitespaces().equals(
+ value)) {
+ err
+ .jspError(
+ n,
+ "jsp.error.page.conflict.trimdirectivewhitespaces",
+ pageInfo.getTrimDirectiveWhitespaces(),
+ value);
+ }
+ }
+ }
+
+ // Check for bad combinations
+ if (pageInfo.getBuffer() == 0 && !pageInfo.isAutoFlush())
+ err.jspError(n, "jsp.error.page.badCombo");
+
+ // Attributes for imports for this node have been processed by
+ // the parsers, just add them to pageInfo.
+ pageInfo.addImports(n.getImports());
+ }
+
+ public void visit(Node.TagDirective n) throws JasperException {
+ // Note: Most of the validation is done in TagFileProcessor
+ // when it created a TagInfo object from the
+ // tag file in which the directive appeared.
+
+ // This method does additional processing to collect page info
+
+ Attributes attrs = n.getAttributes();
+ for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
+ String attr = attrs.getQName(i);
+ String value = attrs.getValue(i);
+
+ if ("language".equals(attr)) {
+ if (pageInfo.getLanguage(false) == null) {
+ pageInfo.setLanguage(value, n, err, false);
+ } else if (!pageInfo.getLanguage(false).equals(value)) {
+ err.jspError(n, "jsp.error.tag.conflict.language",
+ pageInfo.getLanguage(false), value);
+ }
+ } else if ("isELIgnored".equals(attr)) {
+ if (pageInfo.getIsELIgnored() == null) {
+ pageInfo.setIsELIgnored(value, n, err, false);
+ } else if (!pageInfo.getIsELIgnored().equals(value)) {
+ err.jspError(n, "jsp.error.tag.conflict.iselignored",
+ pageInfo.getIsELIgnored(), value);
+ }
+ } else if ("pageEncoding".equals(attr)) {
+ if (pageEncodingSeen)
+ err.jspError(n, "jsp.error.tag.multi.pageencoding");
+ pageEncodingSeen = true;
+ compareTagEncodings(value, n);
+ n.getRoot().setPageEncoding(value);
+ } else if ("deferredSyntaxAllowedAsLiteral".equals(attr)) {
+ if (pageInfo.getDeferredSyntaxAllowedAsLiteral() == null) {
+ pageInfo.setDeferredSyntaxAllowedAsLiteral(value, n,
+ err, false);
+ } else if (!pageInfo.getDeferredSyntaxAllowedAsLiteral()
+ .equals(value)) {
+ err
+ .jspError(
+ n,
+ "jsp.error.tag.conflict.deferredsyntaxallowedasliteral",
+ pageInfo
+ .getDeferredSyntaxAllowedAsLiteral(),
+ value);
+ }
+ } else if ("trimDirectiveWhitespaces".equals(attr)) {
+ if (pageInfo.getTrimDirectiveWhitespaces() == null) {
+ pageInfo.setTrimDirectiveWhitespaces(value, n, err,
+ false);
+ } else if (!pageInfo.getTrimDirectiveWhitespaces().equals(
+ value)) {
+ err
+ .jspError(
+ n,
+ "jsp.error.tag.conflict.trimdirectivewhitespaces",
+ pageInfo.getTrimDirectiveWhitespaces(),
+ value);
+ }
+ }
+ }
+
+ // Attributes for imports for this node have been processed by
+ // the parsers, just add them to pageInfo.
+ pageInfo.addImports(n.getImports());
+ }
+
+ public void visit(Node.AttributeDirective n) throws JasperException {
+ // Do nothing, since this attribute directive has already been
+ // validated by TagFileProcessor when it created a TagInfo object
+ // from the tag file in which the directive appeared
+ }
+
+ public void visit(Node.VariableDirective n) throws JasperException {
+ // Do nothing, since this variable directive has already been
+ // validated by TagFileProcessor when it created a TagInfo object
+ // from the tag file in which the directive appeared
+ }
+
+ /*
+ * Compares page encodings specified in various places, and throws
+ * exception in case of page encoding mismatch.
+ *
+ * @param pageDirEnc The value of the pageEncoding attribute of the page
+ * directive @param pageDir The page directive node
+ *
+ * @throws JasperException in case of page encoding mismatch
+ */
+ private String comparePageEncodings(String thePageDirEnc,
+ Node.PageDirective pageDir) throws JasperException {
+
+ Node.Root root = pageDir.getRoot();
+ String configEnc = root.getJspConfigPageEncoding();
+ String pageDirEnc = thePageDirEnc.toUpperCase();
+
+ /*
+ * Compare the 'pageEncoding' attribute of the page directive with
+ * the encoding specified in the JSP config element whose URL
+ * pattern matches this page. Treat "UTF-16", "UTF-16BE", and
+ * "UTF-16LE" as identical.
+ */
+ if (configEnc != null) {
+ configEnc = configEnc.toUpperCase();
+ if (!pageDirEnc.equals(configEnc)
+ && (!pageDirEnc.startsWith("UTF-16") || !configEnc
+ .startsWith("UTF-16"))) {
+ err.jspError(pageDir,
+ "jsp.error.config_pagedir_encoding_mismatch",
+ configEnc, pageDirEnc);
+ } else {
+ return configEnc;
+ }
+ }
+
+ /*
+ * Compare the 'pageEncoding' attribute of the page directive with
+ * the encoding specified in the XML prolog (only for XML syntax,
+ * and only if JSP document contains XML prolog with encoding
+ * declaration). Treat "UTF-16", "UTF-16BE", and "UTF-16LE" as
+ * identical.
+ */
+ if ((root.isXmlSyntax() && root.isEncodingSpecifiedInProlog()) || root.isBomPresent()) {
+ String pageEnc = root.getPageEncoding().toUpperCase();
+ if (!pageDirEnc.equals(pageEnc)
+ && (!pageDirEnc.startsWith("UTF-16") || !pageEnc
+ .startsWith("UTF-16"))) {
+ err.jspError(pageDir,
+ "jsp.error.prolog_pagedir_encoding_mismatch",
+ pageEnc, pageDirEnc);
+ } else {
+ return pageEnc;
+ }
+ }
+
+ return pageDirEnc;
+ }
+
+ /*
+ * Compares page encodings specified in various places, and throws
+ * exception in case of page encoding mismatch.
+ *
+ * @param thePageDirEnc The value of the pageEncoding attribute of the page
+ * directive @param pageDir The page directive node
+ *
+ * @throws JasperException in case of page encoding mismatch
+ */
+ private void compareTagEncodings(String thePageDirEnc,
+ Node.TagDirective pageDir) throws JasperException {
+
+ Node.Root root = pageDir.getRoot();
+ String pageDirEnc = thePageDirEnc.toUpperCase();
+ /*
+ * Compare the 'pageEncoding' attribute of the page directive with
+ * the encoding specified in the XML prolog (only for XML syntax,
+ * and only if JSP document contains XML prolog with encoding
+ * declaration). Treat "UTF-16", "UTF-16BE", and "UTF-16LE" as
+ * identical.
+ */
+ if ((root.isXmlSyntax() && root.isEncodingSpecifiedInProlog()) || root.isBomPresent()) {
+ String pageEnc = root.getPageEncoding().toUpperCase();
+ if (!pageDirEnc.equals(pageEnc)
+ && (!pageDirEnc.startsWith("UTF-16") || !pageEnc
+ .startsWith("UTF-16"))) {
+ err.jspError(pageDir,
+ "jsp.error.prolog_pagedir_encoding_mismatch",
+ pageEnc, pageDirEnc);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * A visitor for validating nodes other than page directives
+ */
+ static class ValidateVisitor extends Node.Visitor {
+
+ private PageInfo pageInfo;
+
+ private ErrorDispatcher err;
+
+ private TagInfo tagInfo;
+
+ private ClassLoader loader;
+
+ private final StringBuffer buf = new StringBuffer(32);
+
+ private static final JspUtil.ValidAttribute[] jspRootAttrs = {
+ new JspUtil.ValidAttribute("xsi:schemaLocation"),
+ new JspUtil.ValidAttribute("version", true) };
+
+ private static final JspUtil.ValidAttribute[] includeDirectiveAttrs = { new JspUtil.ValidAttribute(
+ "file", true) };
+
+ private static final JspUtil.ValidAttribute[] taglibDirectiveAttrs = {
+ new JspUtil.ValidAttribute("uri"),
+ new JspUtil.ValidAttribute("tagdir"),
+ new JspUtil.ValidAttribute("prefix", true) };
+
+ private static final JspUtil.ValidAttribute[] includeActionAttrs = {
+ new JspUtil.ValidAttribute("page", true, true),
+ new JspUtil.ValidAttribute("flush") };
+
+ private static final JspUtil.ValidAttribute[] paramActionAttrs = {
+ new JspUtil.ValidAttribute("name", true),
+ new JspUtil.ValidAttribute("value", true, true) };
+
+ private static final JspUtil.ValidAttribute[] forwardActionAttrs = { new JspUtil.ValidAttribute(
+ "page", true, true) };
+
+ private static final JspUtil.ValidAttribute[] getPropertyAttrs = {
+ new JspUtil.ValidAttribute("name", true),
+ new JspUtil.ValidAttribute("property", true) };
+
+ private static final JspUtil.ValidAttribute[] setPropertyAttrs = {
+ new JspUtil.ValidAttribute("name", true),
+ new JspUtil.ValidAttribute("property", true),
+ new JspUtil.ValidAttribute("value", false, true),
+ new JspUtil.ValidAttribute("param") };
+
+ private static final JspUtil.ValidAttribute[] useBeanAttrs = {
+ new JspUtil.ValidAttribute("id", true),
+ new JspUtil.ValidAttribute("scope"),
+ new JspUtil.ValidAttribute("class"),
+ new JspUtil.ValidAttribute("type"),
+ new JspUtil.ValidAttribute("beanName", false, true) };
+
+ private static final JspUtil.ValidAttribute[] plugInAttrs = {
+ new JspUtil.ValidAttribute("type", true),
+ new JspUtil.ValidAttribute("code", true),
+ new JspUtil.ValidAttribute("codebase"),
+ new JspUtil.ValidAttribute("align"),
+ new JspUtil.ValidAttribute("archive"),
+ new JspUtil.ValidAttribute("height", false, true),
+ new JspUtil.ValidAttribute("hspace"),
+ new JspUtil.ValidAttribute("jreversion"),
+ new JspUtil.ValidAttribute("name"),
+ new JspUtil.ValidAttribute("vspace"),
+ new JspUtil.ValidAttribute("width", false, true),
+ new JspUtil.ValidAttribute("nspluginurl"),
+ new JspUtil.ValidAttribute("iepluginurl") };
+
+ private static final JspUtil.ValidAttribute[] attributeAttrs = {
+ new JspUtil.ValidAttribute("name", true),
+ new JspUtil.ValidAttribute("trim") };
+
+ private static final JspUtil.ValidAttribute[] invokeAttrs = {
+ new JspUtil.ValidAttribute("fragment", true),
+ new JspUtil.ValidAttribute("var"),
+ new JspUtil.ValidAttribute("varReader"),
+ new JspUtil.ValidAttribute("scope") };
+
+ private static final JspUtil.ValidAttribute[] doBodyAttrs = {
+ new JspUtil.ValidAttribute("var"),
+ new JspUtil.ValidAttribute("varReader"),
+ new JspUtil.ValidAttribute("scope") };
+
+ private static final JspUtil.ValidAttribute[] jspOutputAttrs = {
+ new JspUtil.ValidAttribute("omit-xml-declaration"),
+ new JspUtil.ValidAttribute("doctype-root-element"),
+ new JspUtil.ValidAttribute("doctype-public"),
+ new JspUtil.ValidAttribute("doctype-system") };
+
+ /*
+ * Constructor
+ */
+ ValidateVisitor(Compiler compiler) {
+ this.pageInfo = compiler.getPageInfo();
+ this.err = compiler.getErrorDispatcher();
+ this.tagInfo = compiler.getCompilationContext().getTagInfo();
+ this.loader = compiler.getCompilationContext().getClassLoader();
+ }
+
+ public void visit(Node.JspRoot n) throws JasperException {
+ JspUtil.checkAttributes("Jsp:root", n, jspRootAttrs, err);
+ String version = n.getTextAttribute("version");
+ if (!version.equals("1.2") && !version.equals("2.0") && !version.equals("2.1")) {
+ err.jspError(n, "jsp.error.jsproot.version.invalid", version);
+ }
+ visitBody(n);
+ }
+
+ public void visit(Node.IncludeDirective n) throws JasperException {
+ JspUtil.checkAttributes("Include directive", n,
+ includeDirectiveAttrs, err);
+ visitBody(n);
+ }
+
+ public void visit(Node.TaglibDirective n) throws JasperException {
+ JspUtil.checkAttributes("Taglib directive", n,
+ taglibDirectiveAttrs, err);
+ // Either 'uri' or 'tagdir' attribute must be specified
+ String uri = n.getAttributeValue("uri");
+ String tagdir = n.getAttributeValue("tagdir");
+ if (uri == null && tagdir == null) {
+ err.jspError(n, "jsp.error.taglibDirective.missing.location");
+ }
+ if (uri != null && tagdir != null) {
+ err
+ .jspError(n,
+ "jsp.error.taglibDirective.both_uri_and_tagdir");
+ }
+ }
+
+ public void visit(Node.ParamAction n) throws JasperException {
+ JspUtil.checkAttributes("Param action", n, paramActionAttrs, err);
+ // make sure the value of the 'name' attribute is not a
+ // request-time expression
+ throwErrorIfExpression(n, "name", "jsp:param");
+ n.setValue(getJspAttribute(null, "value", null, null, n
+ .getAttributeValue("value"), java.lang.String.class, n,
+ false));
+ visitBody(n);
+ }
+
+ public void visit(Node.ParamsAction n) throws JasperException {
+ // Make sure we've got at least one nested jsp:param
+ Node.Nodes subElems = n.getBody();
+ if (subElems == null) {
+ err.jspError(n, "jsp.error.params.emptyBody");
+ }
+ visitBody(n);
+ }
+
+ public void visit(Node.IncludeAction n) throws JasperException {
+ JspUtil.checkAttributes("Include action", n, includeActionAttrs,
+ err);
+ n.setPage(getJspAttribute(null, "page", null, null, n
+ .getAttributeValue("page"), java.lang.String.class, n,
+ false));
+ visitBody(n);
+ };
+
+ public void visit(Node.ForwardAction n) throws JasperException {
+ JspUtil.checkAttributes("Forward", n, forwardActionAttrs, err);
+ n.setPage(getJspAttribute(null, "page", null, null, n
+ .getAttributeValue("page"), java.lang.String.class, n,
+ false));
+ visitBody(n);
+ }
+
+ public void visit(Node.GetProperty n) throws JasperException {
+ JspUtil.checkAttributes("GetProperty", n, getPropertyAttrs, err);
+ }
+
+ public void visit(Node.SetProperty n) throws JasperException {
+ JspUtil.checkAttributes("SetProperty", n, setPropertyAttrs, err);
+ String property = n.getTextAttribute("property");
+ String param = n.getTextAttribute("param");
+ String value = n.getAttributeValue("value");
+
+ n.setValue(getJspAttribute(null, "value", null, null, value,
+ java.lang.Object.class, n, false));
+
+ boolean valueSpecified = n.getValue() != null;
+
+ if ("*".equals(property)) {
+ if (param != null || valueSpecified)
+ err.jspError(n, "jsp.error.setProperty.invalid");
+
+ } else if (param != null && valueSpecified) {
+ err.jspError(n, "jsp.error.setProperty.invalid");
+ }
+
+ visitBody(n);
+ }
+
+ public void visit(Node.UseBean n) throws JasperException {
+ JspUtil.checkAttributes("UseBean", n, useBeanAttrs, err);
+
+ String name = n.getTextAttribute("id");
+ String scope = n.getTextAttribute("scope");
+ JspUtil.checkScope(scope, n, err);
+ String className = n.getTextAttribute("class");
+ String type = n.getTextAttribute("type");
+ BeanRepository beanInfo = pageInfo.getBeanRepository();
+
+ if (className == null && type == null)
+ err.jspError(n, "jsp.error.usebean.missingType");
+
+ if (beanInfo.checkVariable(name))
+ err.jspError(n, "jsp.error.usebean.duplicate");
+
+ if ("session".equals(scope) && !pageInfo.isSession())
+ err.jspError(n, "jsp.error.usebean.noSession");
+
+ Node.JspAttribute jattr = getJspAttribute(null, "beanName", null,
+ null, n.getAttributeValue("beanName"),
+ java.lang.String.class, n, false);
+ n.setBeanName(jattr);
+ if (className != null && jattr != null)
+ err.jspError(n, "jsp.error.usebean.notBoth");
+
+ if (className == null)
+ className = type;
+
+ beanInfo.addBean(n, name, className, scope);
+
+ visitBody(n);
+ }
+
+ public void visit(Node.PlugIn n) throws JasperException {
+ JspUtil.checkAttributes("Plugin", n, plugInAttrs, err);
+
+ throwErrorIfExpression(n, "type", "jsp:plugin");
+ throwErrorIfExpression(n, "code", "jsp:plugin");
+ throwErrorIfExpression(n, "codebase", "jsp:plugin");
+ throwErrorIfExpression(n, "align", "jsp:plugin");
+ throwErrorIfExpression(n, "archive", "jsp:plugin");
+ throwErrorIfExpression(n, "hspace", "jsp:plugin");
+ throwErrorIfExpression(n, "jreversion", "jsp:plugin");
+ throwErrorIfExpression(n, "name", "jsp:plugin");
+ throwErrorIfExpression(n, "vspace", "jsp:plugin");
+ throwErrorIfExpression(n, "nspluginurl", "jsp:plugin");
+ throwErrorIfExpression(n, "iepluginurl", "jsp:plugin");
+
+ String type = n.getTextAttribute("type");
+ if (type == null)
+ err.jspError(n, "jsp.error.plugin.notype");
+ if (!type.equals("bean") && !type.equals("applet"))
+ err.jspError(n, "jsp.error.plugin.badtype");
+ if (n.getTextAttribute("code") == null)
+ err.jspError(n, "jsp.error.plugin.nocode");
+
+ Node.JspAttribute width = getJspAttribute(null, "width", null,
+ null, n.getAttributeValue("width"), java.lang.String.class,
+ n, false);
+ n.setWidth(width);
+
+ Node.JspAttribute height = getJspAttribute(null, "height", null,
+ null, n.getAttributeValue("height"),
+ java.lang.String.class, n, false);
+ n.setHeight(height);
+
+ visitBody(n);
+ }
+
+ public void visit(Node.NamedAttribute n) throws JasperException {
+ JspUtil.checkAttributes("Attribute", n, attributeAttrs, err);
+ visitBody(n);
+ }
+
+ public void visit(Node.JspBody n) throws JasperException {
+ visitBody(n);
+ }
+
+ public void visit(Node.Declaration n) throws JasperException {
+ if (pageInfo.isScriptingInvalid()) {
+ err.jspError(n.getStart(), "jsp.error.no.scriptlets");
+ }
+ }
+
+ public void visit(Node.Expression n) throws JasperException {
+ if (pageInfo.isScriptingInvalid()) {
+ err.jspError(n.getStart(), "jsp.error.no.scriptlets");
+ }
+ }
+
+ public void visit(Node.Scriptlet n) throws JasperException {
+ if (pageInfo.isScriptingInvalid()) {
+ err.jspError(n.getStart(), "jsp.error.no.scriptlets");
+ }
+ }
+
+ public void visit(Node.ELExpression n) throws JasperException {
+ // exit if we are ignoring EL all together
+ if (pageInfo.isELIgnored())
+ return;
+
+ // JSP.2.2 - '#{' not allowed in template text
+ if (n.getType() == '#') {
+ if (!pageInfo.isDeferredSyntaxAllowedAsLiteral()
+ && (tagInfo == null
+ || ((tagInfo != null) && !(tagInfo.getTagLibrary().getRequiredVersion().equals("2.0")
+ || tagInfo.getTagLibrary().getRequiredVersion().equals("1.2"))))) {
+ err.jspError(n, "jsp.error.el.template.deferred");
+ } else {
+ return;
+ }
+ }
+
+ // build expression
+ StringBuffer expr = this.getBuffer();
+ expr.append(n.getType()).append('{').append(n.getText())
+ .append('}');
+ ELNode.Nodes el = ELParser.parse(expr.toString());
+
+ // validate/prepare expression
+ prepareExpression(el, n, expr.toString());
+
+ // store it
+ n.setEL(el);
+ }
+
+ public void visit(Node.UninterpretedTag n) throws JasperException {
+ if (n.getNamedAttributeNodes().size() != 0) {
+ err.jspError(n, "jsp.error.namedAttribute.invalidUse");
+ }
+
+ Attributes attrs = n.getAttributes();
+ if (attrs != null) {
+ int attrSize = attrs.getLength();
+ Node.JspAttribute[] jspAttrs = new Node.JspAttribute[attrSize];
+ for (int i = 0; i < attrSize; i++) {
+ jspAttrs[i] = getJspAttribute(null, attrs.getQName(i),
+ attrs.getURI(i), attrs.getLocalName(i), attrs
+ .getValue(i), java.lang.Object.class, n,
+ false);
+ }
+ n.setJspAttributes(jspAttrs);
+ }
+
+ visitBody(n);
+ }
+
+ public void visit(Node.CustomTag n) throws JasperException {
+
+ TagInfo tagInfo = n.getTagInfo();
+ if (tagInfo == null) {
+ err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
+ }
+
+ /*
+ * The bodyconet of a SimpleTag cannot be JSP.
+ */
+ if (n.implementsSimpleTag()
+ && tagInfo.getBodyContent().equalsIgnoreCase(
+ TagInfo.BODY_CONTENT_JSP)) {
+ err.jspError(n, "jsp.error.simpletag.badbodycontent", tagInfo
+ .getTagClassName());
+ }
+
+ /*
+ * If the tag handler declares in the TLD that it supports dynamic
+ * attributes, it also must implement the DynamicAttributes
+ * interface.
+ */
+ if (tagInfo.hasDynamicAttributes()
+ && !n.implementsDynamicAttributes()) {
+ err.jspError(n, "jsp.error.dynamic.attributes.not.implemented",
+ n.getQName());
+ }
+
+ /*
+ * Make sure all required attributes are present, either as
+ * attributes or named attributes (<jsp:attribute>). Also make sure
+ * that the same attribute is not specified in both attributes or
+ * named attributes.
+ */
+ TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
+ String customActionUri = n.getURI();
+ Attributes attrs = n.getAttributes();
+ int attrsSize = (attrs == null) ? 0 : attrs.getLength();
+ for (int i = 0; i < tldAttrs.length; i++) {
+ String attr = null;
+ if (attrs != null) {
+ attr = attrs.getValue(tldAttrs[i].getName());
+ if (attr == null) {
+ attr = attrs.getValue(customActionUri, tldAttrs[i]
+ .getName());
+ }
+ }
+ Node.NamedAttribute na = n.getNamedAttributeNode(tldAttrs[i]
+ .getName());
+
+ if (tldAttrs[i].isRequired() && attr == null && na == null) {
+ err.jspError(n, "jsp.error.missing_attribute", tldAttrs[i]
+ .getName(), n.getLocalName());
+ }
+ if (attr != null && na != null) {
+ err.jspError(n, "jsp.error.duplicate.name.jspattribute",
+ tldAttrs[i].getName());
+ }
+ }
+
+ Node.Nodes naNodes = n.getNamedAttributeNodes();
+ int jspAttrsSize = naNodes.size() + attrsSize;
+ Node.JspAttribute[] jspAttrs = null;
+ if (jspAttrsSize > 0) {
+ jspAttrs = new Node.JspAttribute[jspAttrsSize];
+ }
+ Hashtable<String, Object> tagDataAttrs = new Hashtable<String, Object>(attrsSize);
+
+ checkXmlAttributes(n, jspAttrs, tagDataAttrs);
+ checkNamedAttributes(n, jspAttrs, attrsSize, tagDataAttrs);
+
+ TagData tagData = new TagData(tagDataAttrs);
+
+ // JSP.C1: It is a (translation time) error for an action that
+ // has one or more variable subelements to have a TagExtraInfo
+ // class that returns a non-null object.
+ TagExtraInfo tei = tagInfo.getTagExtraInfo();
+ if (tei != null && tei.getVariableInfo(tagData) != null
+ && tei.getVariableInfo(tagData).length > 0
+ && tagInfo.getTagVariableInfos().length > 0) {
+ err.jspError("jsp.error.non_null_tei_and_var_subelems", n
+ .getQName());
+ }
+
+ n.setTagData(tagData);
+ n.setJspAttributes(jspAttrs);
+
+ visitBody(n);
+ }
+
+ public void visit(Node.JspElement n) throws JasperException {
+
+ Attributes attrs = n.getAttributes();
+ if (attrs == null) {
+ err.jspError(n, "jsp.error.jspelement.missing.name");
+ }
+ int xmlAttrLen = attrs.getLength();
+
+ Node.Nodes namedAttrs = n.getNamedAttributeNodes();
+
+ // XML-style 'name' attribute, which is mandatory, must not be
+ // included in JspAttribute array
+ int jspAttrSize = xmlAttrLen - 1 + namedAttrs.size();
+
+ Node.JspAttribute[] jspAttrs = new Node.JspAttribute[jspAttrSize];
+ int jspAttrIndex = 0;
+
+ // Process XML-style attributes
+ for (int i = 0; i < xmlAttrLen; i++) {
+ if ("name".equals(attrs.getLocalName(i))) {
+ n.setNameAttribute(getJspAttribute(null, attrs.getQName(i),
+ attrs.getURI(i), attrs.getLocalName(i), attrs
+ .getValue(i), java.lang.String.class, n,
+ false));
+ } else {
+ if (jspAttrIndex < jspAttrSize) {
+ jspAttrs[jspAttrIndex++] = getJspAttribute(null, attrs
+ .getQName(i), attrs.getURI(i), attrs
+ .getLocalName(i), attrs.getValue(i),
+ java.lang.Object.class, n, false);
+ }
+ }
+ }
+ if (n.getNameAttribute() == null) {
+ err.jspError(n, "jsp.error.jspelement.missing.name");
+ }
+
+ // Process named attributes
+ for (int i = 0; i < namedAttrs.size(); i++) {
+ Node.NamedAttribute na = (Node.NamedAttribute) namedAttrs
+ .getNode(i);
+ jspAttrs[jspAttrIndex++] = new Node.JspAttribute(na, null,
+ false);
+ }
+
+ n.setJspAttributes(jspAttrs);
+
+ visitBody(n);
+ }
+
+ public void visit(Node.JspOutput n) throws JasperException {
+ JspUtil.checkAttributes("jsp:output", n, jspOutputAttrs, err);
+
+ if (n.getBody() != null) {
+ err.jspError(n, "jsp.error.jspoutput.nonemptybody");
+ }
+
+ String omitXmlDecl = n.getAttributeValue("omit-xml-declaration");
+ String doctypeName = n.getAttributeValue("doctype-root-element");
+ String doctypePublic = n.getAttributeValue("doctype-public");
+ String doctypeSystem = n.getAttributeValue("doctype-system");
+
+ String omitXmlDeclOld = pageInfo.getOmitXmlDecl();
+ String doctypeNameOld = pageInfo.getDoctypeName();
+ String doctypePublicOld = pageInfo.getDoctypePublic();
+ String doctypeSystemOld = pageInfo.getDoctypeSystem();
+
+ if (omitXmlDecl != null && omitXmlDeclOld != null
+ && !omitXmlDecl.equals(omitXmlDeclOld)) {
+ err.jspError(n, "jsp.error.jspoutput.conflict",
+ "omit-xml-declaration", omitXmlDeclOld, omitXmlDecl);
+ }
+
+ if (doctypeName != null && doctypeNameOld != null
+ && !doctypeName.equals(doctypeNameOld)) {
+ err.jspError(n, "jsp.error.jspoutput.conflict",
+ "doctype-root-element", doctypeNameOld, doctypeName);
+ }
+
+ if (doctypePublic != null && doctypePublicOld != null
+ && !doctypePublic.equals(doctypePublicOld)) {
+ err.jspError(n, "jsp.error.jspoutput.conflict",
+ "doctype-public", doctypePublicOld, doctypePublic);
+ }
+
+ if (doctypeSystem != null && doctypeSystemOld != null
+ && !doctypeSystem.equals(doctypeSystemOld)) {
+ err.jspError(n, "jsp.error.jspoutput.conflict",
+ "doctype-system", doctypeSystemOld, doctypeSystem);
+ }
+
+ if (doctypeName == null && doctypeSystem != null
+ || doctypeName != null && doctypeSystem == null) {
+ err.jspError(n, "jsp.error.jspoutput.doctypenamesystem");
+ }
+
+ if (doctypePublic != null && doctypeSystem == null) {
+ err.jspError(n, "jsp.error.jspoutput.doctypepulicsystem");
+ }
+
+ if (omitXmlDecl != null) {
+ pageInfo.setOmitXmlDecl(omitXmlDecl);
+ }
+ if (doctypeName != null) {
+ pageInfo.setDoctypeName(doctypeName);
+ }
+ if (doctypeSystem != null) {
+ pageInfo.setDoctypeSystem(doctypeSystem);
+ }
+ if (doctypePublic != null) {
+ pageInfo.setDoctypePublic(doctypePublic);
+ }
+ }
+
+ public void visit(Node.InvokeAction n) throws JasperException {
+
+ JspUtil.checkAttributes("Invoke", n, invokeAttrs, err);
+
+ String scope = n.getTextAttribute("scope");
+ JspUtil.checkScope(scope, n, err);
+
+ String var = n.getTextAttribute("var");
+ String varReader = n.getTextAttribute("varReader");
+ if (scope != null && var == null && varReader == null) {
+ err.jspError(n, "jsp.error.missing_var_or_varReader");
+ }
+ if (var != null && varReader != null) {
+ err.jspError(n, "jsp.error.var_and_varReader");
+ }
+ }
+
+ public void visit(Node.DoBodyAction n) throws JasperException {
+
+ JspUtil.checkAttributes("DoBody", n, doBodyAttrs, err);
+
+ String scope = n.getTextAttribute("scope");
+ JspUtil.checkScope(scope, n, err);
+
+ String var = n.getTextAttribute("var");
+ String varReader = n.getTextAttribute("varReader");
+ if (scope != null && var == null && varReader == null) {
+ err.jspError(n, "jsp.error.missing_var_or_varReader");
+ }
+ if (var != null && varReader != null) {
+ err.jspError(n, "jsp.error.var_and_varReader");
+ }
+ }
+
+ /*
+ * Make sure the given custom action does not have any invalid
+ * attributes.
+ *
+ * A custom action and its declared attributes always belong to the same
+ * namespace, which is identified by the prefix name of the custom tag
+ * invocation. For example, in this invocation:
+ *
+ * <my:test a="1" b="2" c="3"/>, the action
+ *
+ * "test" and its attributes "a", "b", and "c" all belong to the
+ * namespace identified by the prefix "my". The above invocation would
+ * be equivalent to:
+ *
+ * <my:test my:a="1" my:b="2" my:c="3"/>
+ *
+ * An action attribute may have a prefix different from that of the
+ * action invocation only if the underlying tag handler supports dynamic
+ * attributes, in which case the attribute with the different prefix is
+ * considered a dynamic attribute.
+ */
+ private void checkXmlAttributes(Node.CustomTag n,
+ Node.JspAttribute[] jspAttrs, Hashtable<String, Object> tagDataAttrs)
+ throws JasperException {
+
+ TagInfo tagInfo = n.getTagInfo();
+ if (tagInfo == null) {
+ err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
+ }
+ TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
+ Attributes attrs = n.getAttributes();
+
+ boolean checkDeferred = !pageInfo.isDeferredSyntaxAllowedAsLiteral()
+ && !(tagInfo.getTagLibrary().getRequiredVersion().equals("2.0")
+ || tagInfo.getTagLibrary().getRequiredVersion().equals("1.2"));
+
+ for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
+ boolean found = false;
+
+ boolean runtimeExpression = ((n.getRoot().isXmlSyntax() && attrs.getValue(i).startsWith("%="))
+ || (!n.getRoot().isXmlSyntax() && attrs.getValue(i).startsWith("<%=")));
+ boolean elExpression = false;
+ boolean deferred = false;
+ boolean deferredValueIsLiteral = false;
+
+ ELNode.Nodes el = null;
+ if (!runtimeExpression) {
+ el = ELParser.parse(attrs.getValue(i));
+ Iterator<ELNode> nodes = el.iterator();
+ while (nodes.hasNext()) {
+ ELNode node = nodes.next();
+ if (node instanceof ELNode.Root) {
+ if (((ELNode.Root) node).getType() == '$') {
+ elExpression = true;
+ } else if (checkDeferred && ((ELNode.Root) node).getType() == '#') {
+ elExpression = true;
+ deferred = true;
+ if (pageInfo.isELIgnored()) {
+ deferredValueIsLiteral = true;
+ }
+ }
+ }
+ }
+ }
+
+ boolean expression = runtimeExpression
+ || (elExpression && (!pageInfo.isELIgnored() || (!"true".equalsIgnoreCase(pageInfo.getIsELIgnored()) && checkDeferred && deferred)));
+
+ for (int j = 0; tldAttrs != null && j < tldAttrs.length; j++) {
+ if (attrs.getLocalName(i).equals(tldAttrs[j].getName())
+ && (attrs.getURI(i) == null
+ || attrs.getURI(i).length() == 0 || attrs
+ .getURI(i).equals(n.getURI()))) {
+
+ if (tldAttrs[j].canBeRequestTime()
+ || tldAttrs[j].isDeferredMethod() || tldAttrs[j].isDeferredValue()) { // JSP 2.1
+
+ if (!expression) {
+
+ if (deferredValueIsLiteral && !pageInfo.isDeferredSyntaxAllowedAsLiteral()) {
+ err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
+ tldAttrs[j].getName());
+ }
+
+ String expectedType = null;
+ if (tldAttrs[j].isDeferredMethod()) {
+ // The String litteral must be castable to what is declared as type
+ // for the attribute
+ String m = tldAttrs[j].getMethodSignature();
+ if (m != null) {
+ int rti = m.trim().indexOf(' ');
+ if (rti > 0) {
+ expectedType = m.substring(0, rti).trim();
+ }
+ } else {
+ expectedType = "java.lang.Object";
+ }
+ }
+ if (tldAttrs[j].isDeferredValue()) {
+ // The String litteral must be castable to what is declared as type
+ // for the attribute
+ expectedType = tldAttrs[j].getExpectedTypeName();
+ }
+ if (expectedType != null) {
+ Class expectedClass = String.class;
+ try {
+ expectedClass = JspUtil.toClass(expectedType, loader);
+ } catch (ClassNotFoundException e) {
+ err.jspError
+ (n, "jsp.error.unknown_attribute_type",
+ tldAttrs[j].getName(), expectedType);
+ }
+ // Check casting
+ try {
+ ELSupport.checkType(attrs.getValue(i), expectedClass);
+ } catch (Exception e) {
+ err.jspError
+ (n, "jsp.error.coerce_to_type",
+ tldAttrs[j].getName(), expectedType, attrs.getValue(i));
+ }
+ }
+
+ jspAttrs[i] = new Node.JspAttribute(tldAttrs[j],
+ attrs.getQName(i), attrs.getURI(i), attrs
+ .getLocalName(i),
+ attrs.getValue(i), false, null, false);
+ } else {
+
+ if (deferred && !tldAttrs[j].isDeferredMethod() && !tldAttrs[j].isDeferredValue()) {
+ // No deferred expressions allowed for this attribute
+ err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
+ tldAttrs[j].getName());
+ }
+ if (!deferred && !tldAttrs[j].canBeRequestTime()) {
+ // Only deferred expressions are allowed for this attribute
+ err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
+ tldAttrs[j].getName());
+ }
+
+ Class expectedType = String.class;
+ try {
+ String typeStr = tldAttrs[j].getTypeName();
+ if (tldAttrs[j].isFragment()) {
+ expectedType = JspFragment.class;
+ } else if (typeStr != null) {
+ expectedType = JspUtil.toClass(typeStr,
+ loader);
+ }
+ if (elExpression) {
+ // El expression
+ validateFunctions(el, n);
+ jspAttrs[i] = new Node.JspAttribute(tldAttrs[j],
+ attrs.getQName(i), attrs.getURI(i),
+ attrs.getLocalName(i),
+ attrs.getValue(i), false, el, false);
+ ELContextImpl ctx = new ELContextImpl();
+ ctx.setFunctionMapper(getFunctionMapper(el));
+ try {
+ jspAttrs[i].validateEL(this.pageInfo.getExpressionFactory(), ctx);
+ } catch (ELException e) {
+ this.err.jspError(n.getStart(),
+ "jsp.error.invalid.expression",
+ attrs.getValue(i), e.toString());
+ }
+ } else {
+ // Runtime expression
+ jspAttrs[i] = getJspAttribute(tldAttrs[j],
+ attrs.getQName(i), attrs.getURI(i),
+ attrs.getLocalName(i), attrs
+ .getValue(i), expectedType, n,
+ false);
+ }
+ } catch (ClassNotFoundException e) {
+ err.jspError
+ (n, "jsp.error.unknown_attribute_type",
+ tldAttrs[j].getName(), tldAttrs[j].getTypeName());
+ }
+ }
+
+ } else {
+ // Attribute does not accept any expressions.
+ // Make sure its value does not contain any.
+ if (expression) {
+ err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
+ tldAttrs[j].getName());
+ }
+ jspAttrs[i] = new Node.JspAttribute(tldAttrs[j],
+ attrs.getQName(i), attrs.getURI(i), attrs
+ .getLocalName(i),
+ attrs.getValue(i), false, null, false);
+ }
+ if (expression) {
+ tagDataAttrs.put(attrs.getQName(i),
+ TagData.REQUEST_TIME_VALUE);
+ } else {
+ tagDataAttrs.put(attrs.getQName(i), attrs
+ .getValue(i));
+ }
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if (tagInfo.hasDynamicAttributes()) {
+ jspAttrs[i] = getJspAttribute(null, attrs.getQName(i),
+ attrs.getURI(i), attrs.getLocalName(i), attrs
+ .getValue(i), java.lang.Object.class,
+ n, true);
+ } else {
+ err.jspError(n, "jsp.error.bad_attribute", attrs
+ .getQName(i), n.getLocalName());
+ }
+ }
+ }
+ }
+
+ /*
+ * Make sure the given custom action does not have any invalid named
+ * attributes
+ */
+ private void checkNamedAttributes(Node.CustomTag n,
+ Node.JspAttribute[] jspAttrs, int start,
+ Hashtable<String, Object> tagDataAttrs)
+ throws JasperException {
+
+ TagInfo tagInfo = n.getTagInfo();
+ if (tagInfo == null) {
+ err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
+ }
+ TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
+ Node.Nodes naNodes = n.getNamedAttributeNodes();
+
+ for (int i = 0; i < naNodes.size(); i++) {
+ Node.NamedAttribute na = (Node.NamedAttribute) naNodes
+ .getNode(i);
+ boolean found = false;
+ for (int j = 0; j < tldAttrs.length; j++) {
+ /*
+ * See above comment about namespace matches. For named
+ * attributes, we use the prefix instead of URI as the match
+ * criterion, because in the case of a JSP document, we'd
+ * have to keep track of which namespaces are in scope when
+ * parsing a named attribute, in order to determine the URI
+ * that the prefix of the named attribute's name matches to.
+ */
+ String attrPrefix = na.getPrefix();
+ if (na.getLocalName().equals(tldAttrs[j].getName())
+ && (attrPrefix == null || attrPrefix.length() == 0 || attrPrefix
+ .equals(n.getPrefix()))) {
+ jspAttrs[start + i] = new Node.JspAttribute(na,
+ tldAttrs[j], false);
+ NamedAttributeVisitor nav = null;
+ if (na.getBody() != null) {
+ nav = new NamedAttributeVisitor();
+ na.getBody().visit(nav);
+ }
+ if (nav != null && nav.hasDynamicContent()) {
+ tagDataAttrs.put(na.getName(),
+ TagData.REQUEST_TIME_VALUE);
+ } else {
+ tagDataAttrs.put(na.getName(), na.getText());
+ }
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if (tagInfo.hasDynamicAttributes()) {
+ jspAttrs[start + i] = new Node.JspAttribute(na, null,
+ true);
+ } else {
+ err.jspError(n, "jsp.error.bad_attribute",
+ na.getName(), n.getLocalName());
+ }
+ }
+ }
+ }
+
+ /**
+ * Preprocess attributes that can be expressions. Expression delimiters
+ * are stripped.
+ * <p>
+ * If value is null, checks if there are any NamedAttribute subelements
+ * in the tree node, and if so, constructs a JspAttribute out of a child
+ * NamedAttribute node.
+ */
+ private Node.JspAttribute getJspAttribute(TagAttributeInfo tai,
+ String qName, String uri, String localName, String value,
+ Class expectedType, Node n, boolean dynamic)
+ throws JasperException {
+
+ Node.JspAttribute result = null;
+
+ // XXX Is it an error to see "%=foo%" in non-Xml page?
+ // (We won't see "<%=foo%> in xml page because '<' is not a
+ // valid attribute value in xml).
+
+ if (value != null) {
+ if (n.getRoot().isXmlSyntax() && value.startsWith("%=")) {
+ result = new Node.JspAttribute(tai, qName, uri, localName,
+ value.substring(2, value.length() - 1), true, null,
+ dynamic);
+ } else if (!n.getRoot().isXmlSyntax()
+ && value.startsWith("<%=")) {
+ result = new Node.JspAttribute(tai, qName, uri, localName,
+ value.substring(3, value.length() - 2), true, null,
+ dynamic);
+ } else {
+ // The attribute can contain expressions but is not a
+ // scriptlet expression; thus, we want to run it through
+ // the expression interpreter
+
+ // validate expression syntax if string contains
+ // expression(s)
+ ELNode.Nodes el = ELParser.parse(value);
+
+ boolean deferred = false;
+ Iterator<ELNode> nodes = el.iterator();
+ while (nodes.hasNext()) {
+ ELNode node = nodes.next();
+ if (node instanceof ELNode.Root) {
+ if (((ELNode.Root) node).getType() == '#') {
+ deferred = true;
+ }
+ }
+ }
+
+ if (el.containsEL() && !pageInfo.isELIgnored()
+ && ((!pageInfo.isDeferredSyntaxAllowedAsLiteral() && deferred)
+ || !deferred)) {
+
+ validateFunctions(el, n);
+
+ result = new Node.JspAttribute(tai, qName, uri,
+ localName, value, false, el, dynamic);
+
+ ELContextImpl ctx = new ELContextImpl();
+ ctx.setFunctionMapper(getFunctionMapper(el));
+
+ try {
+ result.validateEL(this.pageInfo
+ .getExpressionFactory(), ctx);
+ } catch (ELException e) {
+ this.err.jspError(n.getStart(),
+ "jsp.error.invalid.expression", value, e
+ .toString());
+ }
+
+ } else {
+ result = new Node.JspAttribute(tai, qName, uri,
+ localName, value, false, null, dynamic);
+ }
+ }
+ } else {
+ // Value is null. Check for any NamedAttribute subnodes
+ // that might contain the value for this attribute.
+ // Otherwise, the attribute wasn't found so we return null.
+
+ Node.NamedAttribute namedAttributeNode = n
+ .getNamedAttributeNode(qName);
+ if (namedAttributeNode != null) {
+ result = new Node.JspAttribute(namedAttributeNode, tai,
+ dynamic);
+ }
+ }
+
+ return result;
+ }
+
+ /*
+ * Return an empty StringBuffer [not thread-safe]
+ */
+ private StringBuffer getBuffer() {
+ this.buf.setLength(0);
+ return this.buf;
+ }
+
+ /*
+ * Checks to see if the given attribute value represents a runtime or EL
+ * expression.
+ */
+ private boolean isExpression(Node n, String value, boolean checkDeferred) {
+
+ boolean runtimeExpression = ((n.getRoot().isXmlSyntax() && value.startsWith("%="))
+ || (!n.getRoot().isXmlSyntax() && value.startsWith("<%=")));
+ boolean elExpression = false;
+
+ if (!runtimeExpression && !pageInfo.isELIgnored()) {
+ Iterator<ELNode> nodes = ELParser.parse(value).iterator();
+ while (nodes.hasNext()) {
+ ELNode node = nodes.next();
+ if (node instanceof ELNode.Root) {
+ if (((ELNode.Root) node).getType() == '$') {
+ elExpression = true;
+ } else if (checkDeferred && !pageInfo.isDeferredSyntaxAllowedAsLiteral()
+ && ((ELNode.Root) node).getType() == '#') {
+ elExpression = true;
+ }
+ }
+ }
+ }
+
+ return runtimeExpression || elExpression;
+
+ }
+
+ /*
+ * Throws exception if the value of the attribute with the given name in
+ * the given node is given as an RT or EL expression, but the spec
+ * requires a static value.
+ */
+ private void throwErrorIfExpression(Node n, String attrName,
+ String actionName) throws JasperException {
+ if (n.getAttributes() != null
+ && n.getAttributes().getValue(attrName) != null
+ && isExpression(n, n.getAttributes().getValue(attrName), true)) {
+ err.jspError(n,
+ "jsp.error.attribute.standard.non_rt_with_expr",
+ attrName, actionName);
+ }
+ }
+
+ private static class NamedAttributeVisitor extends Node.Visitor {
+ private boolean hasDynamicContent;
+
+ public void doVisit(Node n) throws JasperException {
+ if (!(n instanceof Node.JspText)
+ && !(n instanceof Node.TemplateText)) {
+ hasDynamicContent = true;
+ }
+ visitBody(n);
+ }
+
+ public boolean hasDynamicContent() {
+ return hasDynamicContent;
+ }
+ }
+
+ private String findUri(String prefix, Node n) {
+
+ for (Node p = n; p != null; p = p.getParent()) {
+ Attributes attrs = p.getTaglibAttributes();
+ if (attrs == null) {
+ continue;
+ }
+ for (int i = 0; i < attrs.getLength(); i++) {
+ String name = attrs.getQName(i);
+ int k = name.indexOf(':');
+ if (prefix == null && k < 0) {
+ // prefix not specified and a default ns found
+ return attrs.getValue(i);
+ }
+ if (prefix != null && k >= 0
+ && prefix.equals(name.substring(k + 1))) {
+ return attrs.getValue(i);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Validate functions in EL expressions
+ */
+ private void validateFunctions(ELNode.Nodes el, Node n)
+ throws JasperException {
+
+ class FVVisitor extends ELNode.Visitor {
+
+ Node n;
+
+ FVVisitor(Node n) {
+ this.n = n;
+ }
+
+ public void visit(ELNode.Function func) throws JasperException {
+ String prefix = func.getPrefix();
+ String function = func.getName();
+ String uri = null;
+
+ if (n.getRoot().isXmlSyntax()) {
+ uri = findUri(prefix, n);
+ } else if (prefix != null) {
+ uri = pageInfo.getURI(prefix);
+ }
+
+ if (uri == null) {
+ if (prefix == null) {
+ err.jspError(n, "jsp.error.noFunctionPrefix",
+ function);
+ } else {
+ err
+ .jspError(
+ n,
+ "jsp.error.attribute.invalidPrefix",
+ prefix);
+ }
+ }
+ TagLibraryInfo taglib = pageInfo.getTaglib(uri);
+ FunctionInfo funcInfo = null;
+ if (taglib != null) {
+ funcInfo = taglib.getFunction(function);
+ }
+ if (funcInfo == null) {
+ err.jspError(n, "jsp.error.noFunction", function);
+ }
+ // Skip TLD function uniqueness check. Done by Schema ?
+ func.setUri(uri);
+ func.setFunctionInfo(funcInfo);
+ processSignature(func);
+ }
+ }
+
+ el.visit(new FVVisitor(n));
+ }
+
+ private void prepareExpression(ELNode.Nodes el, Node n, String expr)
+ throws JasperException {
+ validateFunctions(el, n);
+
+ // test it out
+ ELContextImpl ctx = new ELContextImpl();
+ ctx.setFunctionMapper(this.getFunctionMapper(el));
+ ExpressionFactory ef = this.pageInfo.getExpressionFactory();
+ try {
+ ef.createValueExpression(ctx, expr, Object.class);
+ } catch (ELException e) {
+
+ }
+ }
+
+ private void processSignature(ELNode.Function func)
+ throws JasperException {
+ func.setMethodName(getMethod(func));
+ func.setParameters(getParameters(func));
+ }
+
+ /**
+ * Get the method name from the signature.
+ */
+ private String getMethod(ELNode.Function func) throws JasperException {
+ FunctionInfo funcInfo = func.getFunctionInfo();
+ String signature = funcInfo.getFunctionSignature();
+
+ int start = signature.indexOf(' ');
+ if (start < 0) {
+ err.jspError("jsp.error.tld.fn.invalid.signature", func
+ .getPrefix(), func.getName());
+ }
+ int end = signature.indexOf('(');
+ if (end < 0) {
+ err.jspError(
+ "jsp.error.tld.fn.invalid.signature.parenexpected",
+ func.getPrefix(), func.getName());
+ }
+ return signature.substring(start + 1, end).trim();
+ }
+
+ /**
+ * Get the parameters types from the function signature.
+ *
+ * @return An array of parameter class names
+ */
+ private String[] getParameters(ELNode.Function func)
+ throws JasperException {
+ FunctionInfo funcInfo = func.getFunctionInfo();
+ String signature = funcInfo.getFunctionSignature();
+ ArrayList<String> params = new ArrayList<String>();
+ // Signature is of the form
+ // <return-type> S <method-name S? '('
+ // < <arg-type> ( ',' <arg-type> )* )? ')'
+ int start = signature.indexOf('(') + 1;
+ boolean lastArg = false;
+ while (true) {
+ int p = signature.indexOf(',', start);
+ if (p < 0) {
+ p = signature.indexOf(')', start);
+ if (p < 0) {
+ err.jspError("jsp.error.tld.fn.invalid.signature", func
+ .getPrefix(), func.getName());
+ }
+ lastArg = true;
+ }
+ String arg = signature.substring(start, p).trim();
+ if (!"".equals(arg)) {
+ params.add(arg);
+ }
+ if (lastArg) {
+ break;
+ }
+ start = p + 1;
+ }
+ return (String[]) params.toArray(new String[params.size()]);
+ }
+
+ private FunctionMapper getFunctionMapper(ELNode.Nodes el)
+ throws JasperException {
+
+ class ValidateFunctionMapper extends FunctionMapper {
+
+ private HashMap<String, Method> fnmap = new HashMap<String, Method>();
+
+ public void mapFunction(String fnQName, Method method) {
+ fnmap.put(fnQName, method);
+ }
+
+ public Method resolveFunction(String prefix, String localName) {
+ return this.fnmap.get(prefix + ":" + localName);
+ }
+ }
+
+ class MapperELVisitor extends ELNode.Visitor {
+ ValidateFunctionMapper fmapper;
+
+ MapperELVisitor(ValidateFunctionMapper fmapper) {
+ this.fmapper = fmapper;
+ }
+
+ public void visit(ELNode.Function n) throws JasperException {
+
+ Class c = null;
+ Method method = null;
+ try {
+ c = loader.loadClass(n.getFunctionInfo()
+ .getFunctionClass());
+ } catch (ClassNotFoundException e) {
+ err.jspError("jsp.error.function.classnotfound", n
+ .getFunctionInfo().getFunctionClass(), n
+ .getPrefix()
+ + ':' + n.getName(), e.getMessage());
+ }
+ String paramTypes[] = n.getParameters();
+ int size = paramTypes.length;
+ Class params[] = new Class[size];
+ int i = 0;
+ try {
+ for (i = 0; i < size; i++) {
+ params[i] = JspUtil.toClass(paramTypes[i], loader);
+ }
+ method = c.getDeclaredMethod(n.getMethodName(), params);
+ } catch (ClassNotFoundException e) {
+ err.jspError("jsp.error.signature.classnotfound",
+ paramTypes[i], n.getPrefix() + ':'
+ + n.getName(), e.getMessage());
+ } catch (NoSuchMethodException e) {
+ err.jspError("jsp.error.noFunctionMethod", n
+ .getMethodName(), n.getName(), c.getName());
+ }
+ fmapper.mapFunction(n.getPrefix() + ':' + n.getName(),
+ method);
+ }
+ }
+
+ ValidateFunctionMapper fmapper = new ValidateFunctionMapper();
+ el.visit(new MapperELVisitor(fmapper));
+ return fmapper;
+ }
+ } // End of ValidateVisitor
+
+ /**
+ * A visitor for validating TagExtraInfo classes of all tags
+ */
+ static class TagExtraInfoVisitor extends Node.Visitor {
+
+ private ErrorDispatcher err;
+
+ /*
+ * Constructor
+ */
+ TagExtraInfoVisitor(Compiler compiler) {
+ this.err = compiler.getErrorDispatcher();
+ }
+
+ public void visit(Node.CustomTag n) throws JasperException {
+ TagInfo tagInfo = n.getTagInfo();
+ if (tagInfo == null) {
+ err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
+ }
+
+ ValidationMessage[] errors = tagInfo.validate(n.getTagData());
+ if (errors != null && errors.length != 0) {
+ StringBuffer errMsg = new StringBuffer();
+ errMsg.append("<h3>");
+ errMsg.append(Localizer.getMessage(
+ "jsp.error.tei.invalid.attributes", n.getQName()));
+ errMsg.append("</h3>");
+ for (int i = 0; i < errors.length; i++) {
+ errMsg.append("<p>");
+ if (errors[i].getId() != null) {
+ errMsg.append(errors[i].getId());
+ errMsg.append(": ");
+ }
+ errMsg.append(errors[i].getMessage());
+ errMsg.append("</p>");
+ }
+
+ err.jspError(n, errMsg.toString());
+ }
+
+ visitBody(n);
+ }
+ }
+
+ public static void validateDirectives(Compiler compiler, Node.Nodes page)
+ throws JasperException {
+ page.visit(new DirectiveVisitor(compiler));
+ }
+
+ public static void validateExDirectives(Compiler compiler, Node.Nodes page)
+ throws JasperException {
+ // Determine the default output content type
+ PageInfo pageInfo = compiler.getPageInfo();
+ String contentType = pageInfo.getContentType();
+
+ if (contentType == null || contentType.indexOf("charset=") < 0) {
+ boolean isXml = page.getRoot().isXmlSyntax();
+ String defaultType;
+ if (contentType == null) {
+ defaultType = isXml ? "text/xml" : "text/html";
+ } else {
+ defaultType = contentType;
+ }
+
+ String charset = null;
+ if (isXml) {
+ charset = "UTF-8";
+ } else {
+ if (!page.getRoot().isDefaultPageEncoding()) {
+ charset = page.getRoot().getPageEncoding();
+ }
+ }
+
+ if (charset != null) {
+ pageInfo.setContentType(defaultType + ";charset=" + charset);
+ } else {
+ pageInfo.setContentType(defaultType);
+ }
+ }
+
+ /*
+ * Validate all other nodes. This validation step includes checking a
+ * custom tag's mandatory and optional attributes against information in
+ * the TLD (first validation step for custom tags according to
+ * JSP.10.5).
+ */
+ page.visit(new ValidateVisitor(compiler));
+
+ /*
+ * Invoke TagLibraryValidator classes of all imported tags (second
+ * validation step for custom tags according to JSP.10.5).
+ */
+ validateXmlView(new PageDataImpl(page, compiler), compiler);
+
+ /*
+ * Invoke TagExtraInfo method isValid() for all imported tags (third
+ * validation step for custom tags according to JSP.10.5).
+ */
+ page.visit(new TagExtraInfoVisitor(compiler));
+
+ }
+
+ // *********************************************************************
+ // Private (utility) methods
+
+ /**
+ * Validate XML view against the TagLibraryValidator classes of all imported
+ * tag libraries.
+ */
+ private static void validateXmlView(PageData xmlView, Compiler compiler)
+ throws JasperException {
+
+ StringBuffer errMsg = null;
+ ErrorDispatcher errDisp = compiler.getErrorDispatcher();
+
+ for (Iterator iter = compiler.getPageInfo().getTaglibs().iterator(); iter
+ .hasNext();) {
+
+ Object o = iter.next();
+ if (!(o instanceof TagLibraryInfoImpl))
+ continue;
+ TagLibraryInfoImpl tli = (TagLibraryInfoImpl) o;
+
+ ValidationMessage[] errors = tli.validate(xmlView);
+ if ((errors != null) && (errors.length != 0)) {
+ if (errMsg == null) {
+ errMsg = new StringBuffer();
+ }
+ errMsg.append("<h3>");
+ errMsg.append(Localizer.getMessage(
+ "jsp.error.tlv.invalid.page", tli.getShortName(),
+ compiler.getPageInfo().getJspFile()));
+ errMsg.append("</h3>");
+ for (int i = 0; i < errors.length; i++) {
+ if (errors[i] != null) {
+ errMsg.append("<p>");
+ errMsg.append(errors[i].getId());
+ errMsg.append(": ");
+ errMsg.append(errors[i].getMessage());
+ errMsg.append("</p>");
+ }
+ }
+ }
+ }
+
+ if (errMsg != null) {
+ errDisp.jspError(errMsg.toString());
+ }
+ }
+}
Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPlugin.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPlugin.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPlugin.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPlugin.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+package org.apache.struts2.jasper.compiler.tagplugin;
+
+/**
+ * This interface is to be implemented by the plugin author, to supply
+ * an alternate implementation of the tag handlers. It can be used to
+ * specify the Java codes to be generated when a tag is invoked.
+ *
+ * An implementation of this interface must be registered in a file
+ * named "tagPlugins.xml" under WEB-INF.
+ */
+
+public interface TagPlugin {
+
+ /**
+ * Generate codes for a custom tag.
+ * @param ctxt a TagPluginContext for accessing Jasper functions
+ */
+ void doTag(TagPluginContext ctxt);
+}
+
Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPluginContext.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPluginContext.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPluginContext.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPluginContext.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+package org.apache.struts2.jasper.compiler.tagplugin;
+
+
+/**
+ * This interface allows the plugin author to make inqueries about the
+ * properties of the current tag, and to use Jasper resources to generate
+ * direct Java codes in place of tag handler invocations.
+ */
+
+public interface TagPluginContext {
+ /**
+ * @return true if the body of the tag is scriptless.
+ */
+ boolean isScriptless();
+
+ /**
+ * @param attribute Name of the attribute
+ * @return true if the attribute is specified in the tag
+ */
+ boolean isAttributeSpecified(String attribute);
+
+ /**
+ * @return An unique temporary variable name that the plugin can use.
+ */
+ String getTemporaryVariableName();
+
+ /**
+ * Generate an import statement
+ * @param s Name of the import class, '*' allowed.
+ */
+ void generateImport(String s);
+
+ /**
+ * Generate a declaration in the of the generated class. This can be
+ * used to declare an innter class, a method, or a class variable.
+ * @param id An unique ID identifying the declaration. It is not
+ * part of the declaration, and is used to ensure that the
+ * declaration will only appear once. If this method is
+ * invoked with the same id more than once in the translation
+ * unit, only the first declaration will be taken.
+ * @param text The text of the declaration.
+ **/
+ void generateDeclaration(String id, String text);
+
+ /**
+ * Generate Java source codes
+ */
+ void generateJavaSource(String s);
+
+ /**
+ * @return true if the attribute is specified and its value is a
+ * translation-time constant.
+ */
+ boolean isConstantAttribute(String attribute);
+
+ /**
+ * @return A string that is the value of a constant attribute. Undefined
+ * if the attribute is not a (translation-time) constant.
+ * null if the attribute is not specified.
+ */
+ String getConstantAttribute(String attribute);
+
+ /**
+ * Generate codesto evaluate value of a attribute in the custom tag
+ * The codes is a Java expression.
+ * NOTE: Currently cannot handle attributes that are fragments.
+ * @param attribute The specified attribute
+ */
+ void generateAttribute(String attribute);
+
+ /**
+ * Generate codes for the body of the custom tag
+ */
+ void generateBody();
+
+ /**
+ * Abandon optimization for this tag handler, and instruct
+ * Jasper to generate the tag handler calls, as usual.
+ * Should be invoked if errors are detected, or when the tag body
+ * is deemed too compilicated for optimization.
+ */
+ void dontUseTagPlugin();
+
+ /**
+ * Get the PluginContext for the parent of this custom tag. NOTE:
+ * The operations available for PluginContext so obtained is limited
+ * to getPluginAttribute and setPluginAttribute, and queries (e.g.
+ * isScriptless(). There should be no calls to generate*().
+ * @return The pluginContext for the parent node.
+ * null if the parent is not a custom tag, or if the pluginConxt
+ * if not available (because useTagPlugin is false, e.g).
+ */
+ TagPluginContext getParentContext();
+
+ /**
+ * Associate the attribute with a value in the current tagplugin context.
+ * The plugin attributes can be used for communication among tags that
+ * must work together as a group. See <c:when> for an example.
+ */
+ void setPluginAttribute(String attr, Object value);
+
+ /**
+ * Get the value of an attribute in the current tagplugin context.
+ */
+ Object getPluginAttribute(String attr);
+}
+