/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* Portions Copyright Apache Software Foundation.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.apache.jasper.compiler;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Iterator;
import java.util.Vector;
import java.util.HashMap;
import javax.servlet.jsp.tagext.TagAttributeInfo;
import javax.servlet.jsp.tagext.TagExtraInfo;
import javax.servlet.jsp.tagext.TagFileInfo;
import javax.servlet.jsp.tagext.TagInfo;
import javax.servlet.jsp.tagext.TagLibraryInfo;
import javax.servlet.jsp.tagext.TagVariableInfo;
import javax.servlet.jsp.tagext.VariableInfo;
import org.apache.jasper.Constants;
import org.apache.jasper.JasperException;
import org.apache.jasper.JspCompilationContext;
import org.apache.jasper.servlet.JspServletWrapper;
import org.apache.jasper.runtime.JspSourceDependent;
/**
* 1. Processes and extracts the directive info in a tag file.
* 2. Compiles and loads tag files used in a JSP file.
*
* @author Kin-man Chung
*/
class TagFileProcessor {
private Vector tempVector;
/**
* A visitor the tag file
*/
private static class TagFileDirectiveVisitor extends Node.Visitor {
private static final JspUtil.ValidAttribute[] tagDirectiveAttrs = {
new JspUtil.ValidAttribute("display-name"),
new JspUtil.ValidAttribute("body-content"),
new JspUtil.ValidAttribute("dynamic-attributes"),
new JspUtil.ValidAttribute("small-icon"),
new JspUtil.ValidAttribute("large-icon"),
new JspUtil.ValidAttribute("description"),
new JspUtil.ValidAttribute("example"),
new JspUtil.ValidAttribute("pageEncoding"),
new JspUtil.ValidAttribute("language"),
new JspUtil.ValidAttribute("import"),
new JspUtil.ValidAttribute("isELIgnored"),
new JspUtil.ValidAttribute("deferredSyntaxAllowedAsLiteral"),
new JspUtil.ValidAttribute("trimDirectiveWhitespaces")
};
private static final JspUtil.ValidAttribute[] attributeDirectiveAttrs = {
new JspUtil.ValidAttribute("name", true),
new JspUtil.ValidAttribute("required"),
new JspUtil.ValidAttribute("fragment"),
new JspUtil.ValidAttribute("rtexprvalue"),
new JspUtil.ValidAttribute("type"),
new JspUtil.ValidAttribute("description"),
new JspUtil.ValidAttribute("deferredValue"),
new JspUtil.ValidAttribute("deferredValueType"),
new JspUtil.ValidAttribute("deferredMethod"),
new JspUtil.ValidAttribute("deferredMethodSignature")
};
private static final JspUtil.ValidAttribute[] variableDirectiveAttrs = {
new JspUtil.ValidAttribute("name-given"),
new JspUtil.ValidAttribute("name-from-attribute"),
new JspUtil.ValidAttribute("alias"),
new JspUtil.ValidAttribute("variable-class"),
new JspUtil.ValidAttribute("scope"),
new JspUtil.ValidAttribute("declare"),
new JspUtil.ValidAttribute("description")
};
private ErrorDispatcher err;
private TagLibraryInfo tagLibInfo;
private String name = null;
private String path = null;
private TagExtraInfo tei = null;
private String bodycontent = null;
private String description = null;
private String displayName = null;
private String smallIcon = null;
private String largeIcon = null;
private String dynamicAttrsMapName;
private String example = null;
private Vector attributeVector;
private Vector variableVector;
private HashMap<String, NameEntry> nameTable =
new HashMap<String, NameEntry>();
private HashMap<String, NameEntry> nameFromTable =
new HashMap<String, NameEntry>();
// The tag file's JSP version
private Double jspVersionDouble;
private static enum Name {
ATTR_NAME("name", "attribute"),
VAR_NAME_GIVEN("name-given", "variable"),
VAR_NAME_FROM("name-from-attribute", "variable"),
VAR_ALIAS("alias", "variable"),
TAG_DYNAMIC("dynamic-attributes", "tag");
private String attribute;
private String directive;
String getAttribute() {
return this.attribute;
}
String getDirective() {
return this.directive;
}
Name(String attribute, String directive) {
this.attribute = attribute;
this.directive = directive;
}
}
public TagFileDirectiveVisitor(Compiler compiler,
TagLibraryInfo tagLibInfo,
String name,
String path) {
err = compiler.getErrorDispatcher();
this.tagLibInfo = tagLibInfo;
this.name = name;
this.path = path;
attributeVector = new Vector();
variableVector = new Vector();
jspVersionDouble = Double.valueOf(tagLibInfo.getRequiredVersion());
}
public void visit(Node.JspRoot n) throws JasperException {
/*
* If a tag file in XML syntax contains a jsp:root element, the
* value of its "version" attribute must match the tag file's JSP
* version.
*/
String jspRootVersion = n.getTextAttribute("version");
if (jspRootVersion == null) {
err.jspError(n, "jsp.error.mandatory.attribute", n.getQName(),
"version");
}
if (!jspRootVersion.equals(jspVersionDouble.toString())) {
err.jspError(n, "jsp.error.tagfile.jspVersionMismatch",
jspRootVersion, jspVersionDouble.toString());
}
visitBody(n);
}
public void visit(Node.TagDirective n) throws JasperException {
JspUtil.checkAttributes("Tag directive", n, tagDirectiveAttrs,
err);
bodycontent = checkConflict(n, bodycontent, "body-content");
if (bodycontent != null &&
!bodycontent.equals(TagInfo.BODY_CONTENT_EMPTY) &&
!bodycontent.equals(TagInfo.BODY_CONTENT_TAG_DEPENDENT) &&
!bodycontent.equals(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
err.jspError(n, "jsp.error.tagdirective.badbodycontent",
bodycontent);
}
dynamicAttrsMapName = checkConflict(n, dynamicAttrsMapName,
"dynamic-attributes");
if (dynamicAttrsMapName != null) {
checkUniqueName(dynamicAttrsMapName, Name.TAG_DYNAMIC, n);
}
smallIcon = checkConflict(n, smallIcon, "small-icon");
largeIcon = checkConflict(n, largeIcon, "large-icon");
description = checkConflict(n, description, "description");
displayName = checkConflict(n, displayName, "display-name");
example = checkConflict(n, example, "example");
if (n.getAttributeValue("deferredSyntaxAllowedAsLiteral") != null
&& Double.compare(jspVersionDouble,
Constants.JSP_VERSION_2_1) < 0) {
err.jspError("jsp.error.invalidTagDirectiveAttrUnless21",
"deferredSyntaxAllowedAsLiteral");
}
// Additional tag directives are validated in Validator
}
private String checkConflict(Node n, String oldAttrValue, String attr)
throws JasperException {
String result = oldAttrValue;
String attrValue = n.getAttributeValue(attr);
if (attrValue != null) {
if (oldAttrValue != null && !oldAttrValue.equals(attrValue)) {
err.jspError(n, "jsp.error.tag.conflict.attr", attr,
oldAttrValue, attrValue);
}
result = attrValue;
}
return result;
}
public void visit(Node.AttributeDirective n) throws JasperException {
JspUtil.checkAttributes("Attribute directive", n,
attributeDirectiveAttrs, err);
String attrName = n.getAttributeValue("name");
boolean required = JspUtil.booleanValue(
n.getAttributeValue("required"));
boolean rtexprvalue = true;
String rtexprvalueString = n.getAttributeValue("rtexprvalue");
if (rtexprvalueString != null) {
rtexprvalue = JspUtil.booleanValue( rtexprvalueString );
}
boolean fragment = JspUtil.booleanValue(
n.getAttributeValue("fragment"));
String type = n.getAttributeValue("type");
String deferredValue = n.getAttributeValue("deferredValue");
String deferredMethod = n.getAttributeValue("deferredMethod");
String expectedType = n.getAttributeValue("deferredValueType");
String methodSignature = n.getAttributeValue("deferredMethodSignature");
if (Double.compare(jspVersionDouble,
Constants.JSP_VERSION_2_1) < 0) {
if (deferredValue != null) {
err.jspError("jsp.error.invalidAttrDirectiveAttrUnless21",
"deferredValue");
}
if (deferredMethod != null) {
err.jspError("jsp.error.invalidAttrDirectiveAttrUnless21",
"deferredMethod");
}
if (expectedType != null) {
err.jspError("jsp.error.invalidAttrDirectiveAttrUnless21",
"deferredValueType");
}
if (methodSignature != null) {
err.jspError("jsp.error.invalidAttrDirectiveAttrUnless21",
"deferredMethodSignature");
}
}
boolean isDeferredValue = JspUtil.booleanValue(deferredValue);
boolean isDeferredMethod = JspUtil.booleanValue(deferredMethod);
if (expectedType == null) {
if (isDeferredValue) {
expectedType = "java.lang.Object";
}
}
else {
if (deferredValue != null && !isDeferredValue) {
err.jspError("jsp.error.deferredvaluewithtype");
}
isDeferredValue = true;
}
if (methodSignature == null) {
if (isDeferredMethod) {
methodSignature = "void method()";
}
}
else {
if (deferredMethod != null && !isDeferredMethod) {
err.jspError("jsp.error.deferredmethodwithsignature");
}
isDeferredMethod = true;
}
if (fragment) {
// type is fixed to "JspFragment" and a translation error
// must occur if specified.
if (type != null) {
err.jspError(n, "jsp.error.fragmentwithtype");
}
// rtexprvalue is fixed to "true" and a translation error
// must occur if specified.
rtexprvalue = true;
if( rtexprvalueString != null ) {
err.jspError(n, "jsp.error.frgmentwithrtexprvalue" );
}
} else if (type == null) {
if (isDeferredValue) {
type = "javax.el.ValueExpression";
} else if (isDeferredMethod) {
type = "javax.el.MethodExpression";
} else {
type = "java.lang.String";
}
} else if (isDeferredValue || isDeferredMethod) {
err.jspError("jsp.error.deferredwithtype");
}
if (isDeferredValue || isDeferredMethod) {
rtexprvalue = false;
}
TagAttributeInfo tagAttributeInfo =
new TagAttributeInfo(attrName,
required,
type,
rtexprvalue,
fragment,
description,
isDeferredValue,
isDeferredMethod,
expectedType,
methodSignature);
attributeVector.addElement(tagAttributeInfo);
checkUniqueName(attrName, Name.ATTR_NAME, n, tagAttributeInfo);
}
public void visit(Node.VariableDirective n) throws JasperException {
JspUtil.checkAttributes("Variable directive", n,
variableDirectiveAttrs, err);
String nameGiven = n.getAttributeValue("name-given");
String nameFromAttribute = n.getAttributeValue("name-from-attribute");
if (nameGiven == null && nameFromAttribute == null) {
err.jspError("jsp.error.variable.either.name");
}
if (nameGiven != null && nameFromAttribute != null) {
err.jspError("jsp.error.variable.both.name");
}
String alias = n.getAttributeValue("alias");
if (nameFromAttribute != null && alias == null ||
nameFromAttribute == null && alias != null) {
err.jspError("jsp.error.variable.alias");
}
String className = n.getAttributeValue("variable-class");
if (className == null)
className = "java.lang.String";
String declareStr = n.getAttributeValue("declare");
boolean declare = true;
if (declareStr != null)
declare = JspUtil.booleanValue(declareStr);
int scope = VariableInfo.NESTED;
String scopeStr = n.getAttributeValue("scope");
if (scopeStr != null) {
if ("NESTED".equals(scopeStr)) {
// Already the default
} else if ("AT_BEGIN".equals(scopeStr)) {
scope = VariableInfo.AT_BEGIN;
} else if ("AT_END".equals(scopeStr)) {
scope = VariableInfo.AT_END;
}
}
if (nameFromAttribute != null) {
/*
* An alias has been specified. We use 'nameGiven' to hold the
* value of the alias, and 'nameFromAttribute' to hold the
* name of the attribute whose value (at invocation-time)
* denotes the name of the variable that is being aliased
*/
nameGiven = alias;
checkUniqueName(nameFromAttribute, Name.VAR_NAME_FROM, n);
checkUniqueName(alias, Name.VAR_ALIAS, n);
}
else {
// name-given specified
checkUniqueName(nameGiven, Name.VAR_NAME_GIVEN, n);
}
variableVector.addElement(new TagVariableInfo(
nameGiven,
nameFromAttribute,
className,
declare,
scope));
}
/*
* Returns the vector of attributes corresponding to attribute
* directives.
*/
public Vector getAttributesVector() {
return attributeVector;
}
/*
* Returns the vector of variables corresponding to variable
* directives.
*/
public Vector getVariablesVector() {
return variableVector;
}
/*
* Returns the value of the dynamic-attributes tag directive
* attribute.
*/
public String getDynamicAttributesMapName() {
return dynamicAttrsMapName;
}
public TagInfo getTagInfo() throws JasperException {
if (name == null) {
// XXX Get it from tag file name
}
if (bodycontent == null) {
bodycontent = TagInfo.BODY_CONTENT_SCRIPTLESS;
}
String tagClassName = JspUtil.getTagHandlerClassName(path, err);
TagVariableInfo[] tagVariableInfos
= new TagVariableInfo[variableVector.size()];
variableVector.copyInto(tagVariableInfos);
TagAttributeInfo[] tagAttributeInfo
= new TagAttributeInfo[attributeVector.size()];
attributeVector.copyInto(tagAttributeInfo);
return new JasperTagInfo(name,
tagClassName,
bodycontent,
description,
tagLibInfo,
tei,
tagAttributeInfo,
displayName,
smallIcon,
largeIcon,
tagVariableInfos,
dynamicAttrsMapName);
}
static class NameEntry {
private Name type;
private Node node;
private TagAttributeInfo attr;
NameEntry(Name type, Node node, TagAttributeInfo attr) {
this.type = type;
this.node = node;
this.attr = attr;
}
Name getType() { return type;}
Node getNode() { return node; }
TagAttributeInfo getTagAttributeInfo() { return attr; }
}
/**
* Reports a translation error if names specified in attributes of
* directives are not unique in this translation unit.
*
* The value of the following attributes must be unique.
* 1. 'name' attribute of an attribute directive
* 2. 'name-given' attribute of a variable directive
* 3. 'alias' attribute of variable directive
* 4. 'dynamic-attributes' of a tag directive
* except that 'dynamic-attributes' can (and must) have the same
* value when it appears in multiple tag directives.
*
* Also, 'name-from' attribute of a variable directive cannot have
* the same value as that from another variable directive.
*/
private void checkUniqueName(String name, Name type, Node n)
throws JasperException {
checkUniqueName(name, type, n, null);
}
private void checkUniqueName(String name, Name type, Node n,
TagAttributeInfo attr)
throws JasperException {
HashMap<String, NameEntry> table =
(type == Name.VAR_NAME_FROM)? nameFromTable: nameTable;
NameEntry nameEntry = table.get(name);
if (nameEntry != null) {
if (type != Name.TAG_DYNAMIC
|| nameEntry.getType() != Name.TAG_DYNAMIC) {
int line = nameEntry.getNode().getStart().getLineNumber();
err.jspError(n, "jsp.error.tagfile.nameNotUnique",
type.getAttribute(), type.getDirective(),
nameEntry.getType().getAttribute(),
nameEntry.getType().getDirective(),
Integer.toString(line));
}
} else {
table.put(name, new NameEntry(type, n, attr));
}
}
/**
* Perform miscelleaneous checks after the nodes are visited.
*/
void postCheck() throws JasperException {
// Check that var.name-from-attributes has valid values.
Iterator iter = nameFromTable.keySet().iterator();
while (iter.hasNext()) {
String nameFrom = (String) iter.next();
NameEntry nameEntry = nameTable.get(nameFrom);
NameEntry nameFromEntry = nameFromTable.get(nameFrom);
Node nameFromNode = nameFromEntry.getNode();
if (nameEntry == null) {
err.jspError(nameFromNode,
"jsp.error.tagfile.nameFrom.noAttribute",
nameFrom);
} else {
Node node = nameEntry.getNode();
TagAttributeInfo tagAttr = nameEntry.getTagAttributeInfo();
if (! "java.lang.String".equals(tagAttr.getTypeName())
|| ! tagAttr.isRequired()
|| tagAttr.canBeRequestTime()){
err.jspError(nameFromNode,
"jsp.error.tagfile.nameFrom.badAttribute",
nameFrom,
Integer.toString(node.getStart().getLineNumber()));
}
}
}
}
}
/**
* Parses the tag file, and collects information on the directives included
* in it. The method is used to obtain the info on the tag file, when the
* handler that it represents is referenced. The tag file is not compiled
* here.
*
* @param pc the current ParserController used in this compilation
* @param name the tag name as specified in the TLD
* @param tagfile the path for the tagfile
* @param tagLibInfo the TagLibraryInfo object associated with this TagInfo
* @return a TagInfo object assembled from the directives in the tag file.
*/
public static TagInfo parseTagFileDirectives(ParserController pc,
String name,
String path,
TagLibraryInfo tagLibInfo)
throws JasperException {
ErrorDispatcher err = pc.getCompiler().getErrorDispatcher();
Node.Nodes page = null;
try {
page = pc.parseTagFileDirectives(path);
} catch (FileNotFoundException e) {
err.jspError("jsp.error.file.not.found", path);
} catch (IOException e) {
err.jspError("jsp.error.file.not.found", path);
}
TagFileDirectiveVisitor tagFileVisitor
= new TagFileDirectiveVisitor(pc.getCompiler(), tagLibInfo, name,
path);
page.visit(tagFileVisitor);
tagFileVisitor.postCheck();
return tagFileVisitor.getTagInfo();
}
/**
* Compiles and loads a tagfile.
*/
private Class loadTagFile(Compiler compiler,
String tagFilePath, TagInfo tagInfo,
PageInfo parentPageInfo)
throws JasperException {
JspCompilationContext ctxt = compiler.getCompilationContext();
JspRuntimeContext rctxt = ctxt.getRuntimeContext();
JspServletWrapper wrapper =
(JspServletWrapper) rctxt.getWrapper(tagFilePath);
synchronized(rctxt) {
if (wrapper == null) {
wrapper = new JspServletWrapper(ctxt.getServletContext(),
ctxt.getOptions(),
tagFilePath,
tagInfo,
ctxt.getRuntimeContext(),
(URL) ctxt.getTagFileJarUrls().get(tagFilePath));
rctxt.addWrapper(tagFilePath,wrapper);
// Use same classloader and classpath for compiling tag files
wrapper.getJspEngineContext().setClassLoader(
(URLClassLoader) ctxt.getClassLoader());
wrapper.getJspEngineContext().setClassPath(ctxt.getClassPath());
}
else {
// Make sure that JspCompilationContext gets the latest TagInfo
// for the tag file. TagInfo instance was created the last
// time the tag file was scanned for directives, and the tag
// file may have been modified since then.
wrapper.getJspEngineContext().setTagInfo(tagInfo);
}
Class tagClazz;
int tripCount = wrapper.incTripCount();
try {
if (tripCount > 0) {
// When tripCount is greater than zero, a circular
// dependency exists. The circularily dependant tag
// file is compiled in prototype mode, to avoid infinite
// recursion.
JspServletWrapper tempWrapper
= new JspServletWrapper(ctxt.getServletContext(),
ctxt.getOptions(),
tagFilePath,
tagInfo,
ctxt.getRuntimeContext(),
(URL) ctxt.getTagFileJarUrls().get(tagFilePath));
tagClazz = tempWrapper.loadTagFilePrototype();
tempVector.add(
tempWrapper.getJspEngineContext().getCompiler());
} else {
tagClazz = wrapper.loadTagFile();
}
} finally {
wrapper.decTripCount();
}
// Add the dependants for this tag file to its parent's
// dependant list. The only reliable dependency information
// can only be obtained from the tag instance.
try {
Object tagIns = tagClazz.newInstance();
if (tagIns instanceof JspSourceDependent) {
Iterator iter =
/* GlassFish Issue 812
((JspSourceDependent)tagIns).getDependants().iterator();
*/
// START GlassFish Issue 812
((java.util.List) ((JspSourceDependent)tagIns).getDependants()).iterator();
// END GlassFish Issue 812
while (iter.hasNext()) {
parentPageInfo.addDependant((String)iter.next());
}
}
} catch (Exception e) {
// ignore errors
}
return tagClazz;
}
}
/*
* Visitor which scans the page and looks for tag handlers that are tag
* files, compiling (if necessary) and loading them.
*/
private class TagFileLoaderVisitor extends Node.Visitor {
private Compiler compiler;
private PageInfo pageInfo;
TagFileLoaderVisitor(Compiler compiler) {
this.compiler = compiler;
this.pageInfo = compiler.getPageInfo();
}
public void visit(Node.CustomTag n) throws JasperException {
TagFileInfo tagFileInfo = n.getTagFileInfo();
if (tagFileInfo != null) {
String tagFilePath = tagFileInfo.getPath();
JspCompilationContext ctxt = compiler.getCompilationContext();
if (ctxt.getTagFileJarUrls().get(tagFilePath) == null) {
// Omit tag file dependency info on jar files for now.
pageInfo.addDependant(tagFilePath);
}
Class c = loadTagFile(compiler, tagFilePath, n.getTagInfo(),
pageInfo);
n.setTagHandlerClass(c);
}
visitBody(n);
}
}
/**
* Implements a phase of the translation that compiles (if necessary)
* the tag files used in a JSP files. The directives in the tag files
* are assumed to have been proccessed and encapsulated as TagFileInfo
* in the CustomTag nodes.
*/
public void loadTagFiles(Compiler compiler, Node.Nodes page)
throws JasperException {
tempVector = new Vector();
page.visit(new TagFileLoaderVisitor(compiler));
}
/**
* Removed the java and class files for the tag prototype
* generated from the current compilation.
* @param classFileName If non-null, remove only the class file with
* with this name.
*/
public void removeProtoTypeFiles(String classFileName) {
Iterator iter = tempVector.iterator();
while (iter.hasNext()) {
Compiler c = (Compiler) iter.next();
if (classFileName == null) {
c.removeGeneratedClassFiles();
} else if (classFileName.equals(
c.getCompilationContext().getClassFileName())) {
c.removeGeneratedClassFiles();
tempVector.remove(c);
return;
}
}
}
}
|