TagPluginManager.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jasper.compiler;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import jakarta.servlet.ServletContext;
import org.apache.jasper.Constants;
import org.apache.jasper.JasperException;
import org.apache.jasper.compiler.tagplugin.TagPlugin;
import org.apache.jasper.compiler.tagplugin.TagPluginContext;
import org.apache.tomcat.util.descriptor.tagplugin.TagPluginParser;
import org.xml.sax.SAXException;
/**
* Manages tag plugin optimizations.
*
* @author Kin-man Chung
*/
public class TagPluginManager {
private static final String META_INF_JASPER_TAG_PLUGINS_XML =
"META-INF/org.apache.jasper/tagPlugins.xml";
private static final String TAG_PLUGINS_XML = "/WEB-INF/tagPlugins.xml";
private final ServletContext ctxt;
private HashMap<String, TagPlugin> tagPlugins;
private boolean initialized = false;
public TagPluginManager(ServletContext ctxt) {
this.ctxt = ctxt;
}
public void apply(Node.Nodes page, ErrorDispatcher err, PageInfo pageInfo)
throws JasperException {
init(err);
if (!tagPlugins.isEmpty()) {
page.visit(new NodeVisitor(this, pageInfo));
}
}
private void init(ErrorDispatcher err) throws JasperException {
if (initialized) {
return;
}
String blockExternalString = ctxt.getInitParameter(
Constants.XML_BLOCK_EXTERNAL_INIT_PARAM);
boolean blockExternal;
if (blockExternalString == null) {
blockExternal = true;
} else {
blockExternal = Boolean.parseBoolean(blockExternalString);
}
TagPluginParser parser;
Thread currentThread = Thread.currentThread();
ClassLoader original = currentThread.getContextClassLoader();
try {
currentThread.setContextClassLoader(TagPluginManager.class.getClassLoader());
parser = new TagPluginParser(ctxt, blockExternal);
Enumeration<URL> urls =
ctxt.getClassLoader().getResources(META_INF_JASPER_TAG_PLUGINS_XML);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
parser.parse(url);
}
URL url = ctxt.getResource(TAG_PLUGINS_XML);
if (url != null) {
parser.parse(url);
}
} catch (IOException | SAXException e) {
throw new JasperException(e);
} finally {
currentThread.setContextClassLoader(original);
}
Map<String, String> plugins = parser.getPlugins();
tagPlugins = new HashMap<>(plugins.size());
for (Map.Entry<String, String> entry : plugins.entrySet()) {
try {
String tagClass = entry.getKey();
String pluginName = entry.getValue();
Class<?> pluginClass = ctxt.getClassLoader().loadClass(pluginName);
TagPlugin plugin = (TagPlugin) pluginClass.getConstructor().newInstance();
tagPlugins.put(tagClass, plugin);
} catch (Exception e) {
err.jspError(e);
}
}
initialized = true;
}
/**
* Invoke tag plugin for the given custom tag, if a plugin exists for
* the custom tag's tag handler.
* <p/>
* The given custom tag node will be manipulated by the plugin.
*/
private void invokePlugin(Node.CustomTag n, PageInfo pageInfo) {
TagPlugin tagPlugin = tagPlugins.get(n.getTagHandlerClass().getName());
if (tagPlugin == null) {
return;
}
TagPluginContext tagPluginContext = new TagPluginContextImpl(n, pageInfo);
n.setTagPluginContext(tagPluginContext);
tagPlugin.doTag(tagPluginContext);
}
private static class NodeVisitor extends Node.Visitor {
private final TagPluginManager manager;
private final PageInfo pageInfo;
NodeVisitor(TagPluginManager manager, PageInfo pageInfo) {
this.manager = manager;
this.pageInfo = pageInfo;
}
@Override
public void visit(Node.CustomTag n) throws JasperException {
manager.invokePlugin(n, pageInfo);
visitBody(n);
}
}
private static class TagPluginContextImpl implements TagPluginContext {
private final Node.CustomTag node;
private final PageInfo pageInfo;
private final HashMap<String, Object> pluginAttributes;
private Node.Nodes curNodes;
TagPluginContextImpl(Node.CustomTag n, PageInfo pageInfo) {
this.node = n;
this.pageInfo = pageInfo;
curNodes = new Node.Nodes();
n.setAtETag(curNodes);
curNodes = new Node.Nodes();
n.setAtSTag(curNodes);
n.setUseTagPlugin(true);
pluginAttributes = new HashMap<>();
}
@Override
public TagPluginContext getParentContext() {
Node parent = node.getParent();
if (!(parent instanceof Node.CustomTag)) {
return null;
}
return ((Node.CustomTag) parent).getTagPluginContext();
}
@Override
public void setPluginAttribute(String key, Object value) {
pluginAttributes.put(key, value);
}
@Override
public Object getPluginAttribute(String key) {
return pluginAttributes.get(key);
}
@Override
public boolean isScriptless() {
return node.getChildInfo().isScriptless();
}
@Override
public boolean isConstantAttribute(String attribute) {
Node.JspAttribute attr = getNodeAttribute(attribute);
if (attr == null) {
return false;
}
return attr.isLiteral();
}
@Override
public String getConstantAttribute(String attribute) {
Node.JspAttribute attr = getNodeAttribute(attribute);
if (attr == null) {
return null;
}
return attr.getValue();
}
@Override
public boolean isAttributeSpecified(String attribute) {
return getNodeAttribute(attribute) != null;
}
@Override
public String getTemporaryVariableName() {
return node.getRoot().nextTemporaryVariableName();
}
@Override
public void generateImport(String imp) {
pageInfo.addImport(imp);
}
@Override
public void generateDeclaration(String id, String text) {
if (pageInfo.isPluginDeclared(id)) {
return;
}
curNodes.add(new Node.Declaration(text, node.getStart(), null));
}
@Override
public void generateJavaSource(String sourceCode) {
curNodes.add(new Node.Scriptlet(sourceCode, node.getStart(),
null));
}
@Override
public void generateAttribute(String attributeName) {
curNodes.add(new Node.AttributeGenerator(node.getStart(),
attributeName,
node));
}
@Override
public void dontUseTagPlugin() {
node.setUseTagPlugin(false);
}
@Override
public void generateBody() {
// Since we'll generate the body anyway, this is really a nop,
// except for the fact that it lets us put the Java sources the
// plugins produce in the correct order (w.r.t the body).
curNodes = node.getAtETag();
}
@Override
public boolean isTagFile() {
return pageInfo.isTagFile();
}
private Node.JspAttribute getNodeAttribute(String attribute) {
Node.JspAttribute[] attrs = node.getJspAttributes();
for (int i = 0; attrs != null && i < attrs.length; i++) {
if (attrs[i].getName().equals(attribute)) {
return attrs[i];
}
}
return null;
}
}
}