/* | |
* 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 org.apache.struts2.jasper.JasperException; | |
import org.apache.struts2.jasper.JspCompilationContext; | |
import org.apache.struts2.jasper.runtime.JspSourceDependent; | |
import org.apache.struts2.jasper.servlet.JspServletWrapper; | |
import javax.servlet.jsp.tagext.*; | |
import java.io.FileNotFoundException; | |
import java.io.IOException; | |
import java.net.URL; | |
import java.net.URLClassLoader; | |
import java.util.HashMap; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Vector; | |
/** | |
* 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") }; | |
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") | |
}; | |
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 static final String ATTR_NAME = | |
"the name attribute of the attribute directive"; | |
private static final String VAR_NAME_GIVEN = | |
"the name-given attribute of the variable directive"; | |
private static final String VAR_NAME_FROM = | |
"the name-from-attribute attribute of the variable directive"; | |
private static final String VAR_ALIAS = | |
"the alias attribute of the variable directive"; | |
private static final String TAG_DYNAMIC = | |
"the dynamic-attributes attribute of the tag directive"; | |
private HashMap nameTable = new HashMap(); | |
private HashMap nameFromTable = new HashMap(); | |
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(); | |
} | |
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.equalsIgnoreCase(TagInfo.BODY_CONTENT_EMPTY) && | |
!bodycontent.equalsIgnoreCase(TagInfo.BODY_CONTENT_TAG_DEPENDENT) && | |
!bodycontent.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) { | |
err.jspError(n, "jsp.error.tagdirective.badbodycontent", | |
bodycontent); | |
} | |
dynamicAttrsMapName = checkConflict(n, dynamicAttrsMapName, | |
"dynamic-attributes"); | |
if (dynamicAttrsMapName != null) { | |
checkUniqueName(dynamicAttrsMapName, 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"); | |
} | |
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"); | |
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) | |
type = "java.lang.String"; | |
} | |
TagAttributeInfo tagAttributeInfo = | |
new TagAttributeInfo(attrName, required, type, rtexprvalue, | |
fragment); | |
attributeVector.addElement(tagAttributeInfo); | |
checkUniqueName(attrName, 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, VAR_NAME_FROM, n); | |
checkUniqueName(alias, VAR_ALIAS, n); | |
} | |
else { | |
// name-given specified | |
checkUniqueName(nameGiven, 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 String type; | |
private Node node; | |
private TagAttributeInfo attr; | |
NameEntry(String type, Node node, TagAttributeInfo attr) { | |
this.type = type; | |
this.node = node; | |
this.attr = attr; | |
} | |
String 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, String type, Node n) | |
throws JasperException { | |
checkUniqueName(name, type, n, null); | |
} | |
private void checkUniqueName(String name, String type, Node n, | |
TagAttributeInfo attr) | |
throws JasperException { | |
HashMap table = (type == VAR_NAME_FROM)? nameFromTable: nameTable; | |
NameEntry nameEntry = (NameEntry) table.get(name); | |
if (nameEntry != null) { | |
if (type != TAG_DYNAMIC || nameEntry.getType() != TAG_DYNAMIC) { | |
int line = nameEntry.getNode().getStart().getLineNumber(); | |
err.jspError(n, "jsp.error.tagfile.nameNotUnique", | |
type, nameEntry.getType(), Integer.toString(line)); | |
} | |
} else { | |
table.put(name, new NameEntry(type, n, attr)); | |
} | |
} | |
/** | |
* Perform miscellean 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 = (NameEntry) nameTable.get(nameFrom); | |
NameEntry nameFromEntry = | |
(NameEntry) 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 = | |
((List) ((JspSourceDependent) tagIns).getDependants()).iterator(); | |
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; | |
} | |
} | |
} | |
} | |