diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/Constants.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/Constants.java new file mode 100644 index 000000000..6fbf52e8d --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/Constants.java @@ -0,0 +1,208 @@ +/* + * 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; + + +/** + * Some constants and other global data that are used by the compiler and the runtime. + * + * @author Anil K. Vijendran + * @author Harish Prabandham + * @author Shawn Bayern + * @author Mark Roth + */ +public class Constants { + + /** + * The base class of the generated servlets. + */ + public static final String JSP_SERVLET_BASE = + System.getProperty("org.apache.jasper.Constants.JSP_SERVLET_BASE", "org.apache.jasper.runtime.HttpJspBase"); + + /** + * _jspService is the name of the method that is called by + * HttpJspBase.service(). This is where most of the code generated + * from JSPs go. + */ + public static final String SERVICE_METHOD_NAME = + System.getProperty("org.apache.jasper.Constants.SERVICE_METHOD_NAME", "_jspService"); + + /** + * Default servlet content type. + */ + public static final String SERVLET_CONTENT_TYPE = "text/html"; + + /** + * These classes/packages are automatically imported by the + * generated code. + */ + public static final String[] STANDARD_IMPORTS = { + "javax.servlet.*", + "javax.servlet.http.*", + "javax.servlet.jsp.*" + }; + + /** + * ServletContext attribute for classpath. This is tomcat specific. + * Other servlet engines may choose to support this attribute if they + * want to have this JSP engine running on them. + */ + public static final String SERVLET_CLASSPATH = + System.getProperty("org.apache.jasper.Constants.SERVLET_CLASSPATH", "org.apache.catalina.jsp_classpath"); + + /** + * Request attribute for <jsp-file> element of a + * servlet definition. If present on a request, this overrides the + * value returned by request.getServletPath() to select + * the JSP page to be executed. + */ + public static final String JSP_FILE = + System.getProperty("org.apache.jasper.Constants.JSP_FILE", "org.apache.catalina.jsp_file"); + + + /** + * Default size of the JSP buffer. + */ + public static final int DEFAULT_BUFFER_SIZE = 8 * 1024; + + /** + * Default size for the tag buffers. + */ + public static final int DEFAULT_TAG_BUFFER_SIZE = 512; + + /** + * Default tag handler pool size. + */ + public static final int MAX_POOL_SIZE = 5; + + /** + * The query parameter that causes the JSP engine to just + * pregenerated the servlet but not invoke it. + */ + public static final String PRECOMPILE = + System.getProperty("org.apache.jasper.Constants.PRECOMPILE", "jsp_precompile"); + + /** + * The default package name for compiled jsp pages. + */ + public static final String JSP_PACKAGE_NAME = + System.getProperty("org.apache.jasper.Constants.JSP_PACKAGE_NAME", "org.apache.jsp"); + + /** + * The default package name for tag handlers generated from tag files + */ + public static final String TAG_FILE_PACKAGE_NAME = + System.getProperty("org.apache.jasper.Constants.TAG_FILE_PACKAGE_NAME", "org.apache.jsp.tag"); + + /** + * Servlet context and request attributes that the JSP engine + * uses. + */ + public static final String INC_SERVLET_PATH = "javax.servlet.include.servlet_path"; + public static final String TMP_DIR = "javax.servlet.context.tempdir"; + + // Must be kept in sync with org/apache/catalina/Globals.java + public static final String ALT_DD_ATTR = + System.getProperty("org.apache.jasper.Constants.ALT_DD_ATTR", "org.apache.catalina.deploy.alt_dd"); + + /** + * Public Id and the Resource path (of the cached copy) + * of the DTDs for tag library descriptors. + */ + public static final String TAGLIB_DTD_PUBLIC_ID_11 = + "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"; + public static final String TAGLIB_DTD_RESOURCE_PATH_11 = + "/javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd"; + public static final String TAGLIB_DTD_PUBLIC_ID_12 = + "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"; + public static final String TAGLIB_DTD_RESOURCE_PATH_12 = + "/javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd"; + + /** + * Public Id and the Resource path (of the cached copy) + * of the DTDs for web application deployment descriptors + */ + public static final String WEBAPP_DTD_PUBLIC_ID_22 = + "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"; + public static final String WEBAPP_DTD_RESOURCE_PATH_22 = + "/javax/servlet/resources/web-app_2_2.dtd"; + public static final String WEBAPP_DTD_PUBLIC_ID_23 = + "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"; + public static final String WEBAPP_DTD_RESOURCE_PATH_23 = + "/javax/servlet/resources/web-app_2_3.dtd"; + + /** + * List of the Public IDs that we cache, and their + * associated location. This is used by + * an EntityResolver to return the location of the + * cached copy of a DTD. + */ + public static final String[] CACHED_DTD_PUBLIC_IDS = { + TAGLIB_DTD_PUBLIC_ID_11, + TAGLIB_DTD_PUBLIC_ID_12, + WEBAPP_DTD_PUBLIC_ID_22, + WEBAPP_DTD_PUBLIC_ID_23, + }; + public static final String[] CACHED_DTD_RESOURCE_PATHS = { + TAGLIB_DTD_RESOURCE_PATH_11, + TAGLIB_DTD_RESOURCE_PATH_12, + WEBAPP_DTD_RESOURCE_PATH_22, + WEBAPP_DTD_RESOURCE_PATH_23, + }; + + /** + * Default URLs to download the pluging for Netscape and IE. + */ + public static final String NS_PLUGIN_URL = + "http://java.sun.com/products/plugin/"; + + public static final String IE_PLUGIN_URL = + "http://java.sun.com/products/plugin/1.2.2/jinstall-1_2_2-win.cab#Version=1,2,2,0"; + + /** + * Prefix to use for generated temporary variable names + */ + public static final String TEMP_VARIABLE_NAME_PREFIX = + System.getProperty("org.apache.jasper.Constants.TEMP_VARIABLE_NAME_PREFIX", "_jspx_temp"); + + /** + * A replacement char for "\$". + * XXX This is a hack to avoid changing EL interpreter to recognize "\$" + * @deprecated + */ + public static final char ESC = '\u001b'; + /** + * @deprecated + */ + public static final String ESCStr = "'\\u001b'"; + + /** + * Has security been turned on? + */ + public static final boolean IS_SECURITY_ENABLED = + (System.getSecurityManager() != null); + + /** + * The name of the path parameter used to pass the session identifier + * back and forth with the client. + */ + public static final String SESSION_PARAMETER_NAME = + System.getProperty("org.apache.catalina.SESSION_PARAMETER_NAME", + "jsessionid"); + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/EmbeddedServletOptions.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/EmbeddedServletOptions.java new file mode 100644 index 000000000..17ec10891 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/EmbeddedServletOptions.java @@ -0,0 +1,673 @@ +/* + * 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; + +import java.io.File; +import java.util.*; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; + +import org.apache.jasper.compiler.TldLocationsCache; +import org.apache.jasper.compiler.JspConfig; +import org.apache.jasper.compiler.TagPluginManager; +import org.apache.jasper.compiler.Localizer; +import org.apache.jasper.xmlparser.ParserUtils; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +/** + * A class to hold all init parameters specific to the JSP engine. + * + * @author Anil K. Vijendran + * @author Hans Bergsten + * @author Pierre Delisle + */ +public final class EmbeddedServletOptions implements Options { + + // Logger + private Log log = LogFactory.getLog(EmbeddedServletOptions.class); + + private Properties settings = new Properties(); + + /** + * Is Jasper being used in development mode? + */ + private boolean development = true; + + /** + * Should Ant fork its java compiles of JSP pages. + */ + public boolean fork = true; + + /** + * Do you want to keep the generated Java files around? + */ + private boolean keepGenerated = true; + + /** + * Should white spaces between directives or actions be trimmed? + */ + private boolean trimSpaces = false; + + /** + * Determines whether tag handler pooling is enabled. + */ + private boolean isPoolingEnabled = true; + + /** + * Do you want support for "mapped" files? This will generate + * servlet that has a print statement per line of the JSP file. + * This seems like a really nice feature to have for debugging. + */ + private boolean mappedFile = true; + + /** + * Do we want to include debugging information in the class file? + */ + private boolean classDebugInfo = true; + + /** + * Background compile thread check interval in seconds. + */ + private int checkInterval = 0; + + /** + * Is the generation of SMAP info for JSR45 debuggin suppressed? + */ + private boolean isSmapSuppressed = false; + + /** + * Should SMAP info for JSR45 debugging be dumped to a file? + */ + private boolean isSmapDumped = false; + + /** + * Are Text strings to be generated as char arrays? + */ + private boolean genStringAsCharArray = false; + + private boolean errorOnUseBeanInvalidClassAttribute = true; + + /** + * I want to see my generated servlets. Which directory are they + * in? + */ + private File scratchDir; + + /** + * Need to have this as is for versions 4 and 5 of IE. Can be set from + * the initParams so if it changes in the future all that is needed is + * to have a jsp initParam of type ieClassId="" + */ + private String ieClassId = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"; + + /** + * What classpath should I use while compiling generated servlets? + */ + private String classpath = null; + + /** + * Compiler to use. + */ + private String compiler = null; + + /** + * Compiler target VM. + */ + private String compilerTargetVM = "1.5"; + + /** + * The compiler source VM. + */ + private String compilerSourceVM = "1.5"; + + /** + * The compiler class name. + */ + private String compilerClassName = null; + + /** + * Cache for the TLD locations + */ + private TldLocationsCache tldLocationsCache = null; + + /** + * Jsp config information + */ + private JspConfig jspConfig = null; + + /** + * TagPluginManager + */ + private TagPluginManager tagPluginManager = null; + + /** + * Java platform encoding to generate the JSP + * page servlet. + */ + private String javaEncoding = "UTF8"; + + /** + * Modification test interval. + */ + private int modificationTestInterval = 4; + + /** + * Is generation of X-Powered-By response header enabled/disabled? + */ + private boolean xpoweredBy; + + /** + * Should we include a source fragment in exception messages, which could be displayed + * to the developer ? + */ + private boolean displaySourceFragment = true; + + + public String getProperty(String name ) { + return settings.getProperty( name ); + } + + public void setProperty(String name, String value ) { + if (name != null && value != null){ + settings.setProperty( name, value ); + } + } + + /** + * Are we keeping generated code around? + */ + public boolean getKeepGenerated() { + return keepGenerated; + } + + /** + * Should white spaces between directives or actions be trimmed? + */ + public boolean getTrimSpaces() { + return trimSpaces; + } + + public boolean isPoolingEnabled() { + return isPoolingEnabled; + } + + /** + * Are we supporting HTML mapped servlets? + */ + public boolean getMappedFile() { + return mappedFile; + } + + /** + * Should errors be sent to client or thrown into stderr? + * @deprecated + */ + @Deprecated + public boolean getSendErrorToClient() { + return true; + } + + /** + * Should class files be compiled with debug information? + */ + public boolean getClassDebugInfo() { + return classDebugInfo; + } + + /** + * Background JSP compile thread check intervall + */ + public int getCheckInterval() { + return checkInterval; + } + + /** + * Modification test interval. + */ + public int getModificationTestInterval() { + return modificationTestInterval; + } + + /** + * Is Jasper being used in development mode? + */ + public boolean getDevelopment() { + return development; + } + + /** + * Is the generation of SMAP info for JSR45 debuggin suppressed? + */ + public boolean isSmapSuppressed() { + return isSmapSuppressed; + } + + /** + * Should SMAP info for JSR45 debugging be dumped to a file? + */ + public boolean isSmapDumped() { + return isSmapDumped; + } + + /** + * Are Text strings to be generated as char arrays? + */ + public boolean genStringAsCharArray() { + return this.genStringAsCharArray; + } + + /** + * Class ID for use in the plugin tag when the browser is IE. + */ + public String getIeClassId() { + return ieClassId; + } + + /** + * What is my scratch dir? + */ + public File getScratchDir() { + return scratchDir; + } + + /** + * What classpath should I use while compiling the servlets + * generated from JSP files? + */ + public String getClassPath() { + return classpath; + } + + /** + * Is generation of X-Powered-By response header enabled/disabled? + */ + public boolean isXpoweredBy() { + return xpoweredBy; + } + + /** + * Compiler to use. + */ + public String getCompiler() { + return compiler; + } + + /** + * @see Options#getCompilerTargetVM + */ + public String getCompilerTargetVM() { + return compilerTargetVM; + } + + /** + * @see Options#getCompilerSourceVM + */ + public String getCompilerSourceVM() { + return compilerSourceVM; + } + + /** + * Java compiler class to use. + */ + public String getCompilerClassName() { + return compilerClassName; + } + + public boolean getErrorOnUseBeanInvalidClassAttribute() { + return errorOnUseBeanInvalidClassAttribute; + } + + public void setErrorOnUseBeanInvalidClassAttribute(boolean b) { + errorOnUseBeanInvalidClassAttribute = b; + } + + public TldLocationsCache getTldLocationsCache() { + return tldLocationsCache; + } + + public void setTldLocationsCache( TldLocationsCache tldC ) { + tldLocationsCache = tldC; + } + + public String getJavaEncoding() { + return javaEncoding; + } + + public boolean getFork() { + return fork; + } + + public JspConfig getJspConfig() { + return jspConfig; + } + + public TagPluginManager getTagPluginManager() { + return tagPluginManager; + } + + public boolean isCaching() { + return false; + } + + public Map getCache() { + return null; + } + + /** + * Should we include a source fragment in exception messages, which could be displayed + * to the developer ? + */ + public boolean getDisplaySourceFragment() { + return displaySourceFragment; + } + + /** + * Create an EmbeddedServletOptions object using data available from + * ServletConfig and ServletContext. + */ + public EmbeddedServletOptions(ServletConfig config, + ServletContext context) { + + // JVM version numbers + try { + if (Float.parseFloat(System.getProperty("java.specification.version")) > 1.4) { + compilerSourceVM = compilerTargetVM = "1.5"; + } else { + compilerSourceVM = compilerTargetVM = "1.4"; + } + } catch (NumberFormatException e) { + // Ignore + } + + Enumeration enumeration=config.getInitParameterNames(); + while( enumeration.hasMoreElements() ) { + String k=(String)enumeration.nextElement(); + String v=config.getInitParameter( k ); + setProperty( k, v); + } + + // quick hack + String validating=config.getInitParameter( "validating"); + if( "false".equals( validating )) ParserUtils.validating=false; + + String keepgen = config.getInitParameter("keepgenerated"); + if (keepgen != null) { + if (keepgen.equalsIgnoreCase("true")) { + this.keepGenerated = true; + } else if (keepgen.equalsIgnoreCase("false")) { + this.keepGenerated = false; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.keepgen")); + } + } + } + + + String trimsp = config.getInitParameter("trimSpaces"); + if (trimsp != null) { + if (trimsp.equalsIgnoreCase("true")) { + trimSpaces = true; + } else if (trimsp.equalsIgnoreCase("false")) { + trimSpaces = false; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.trimspaces")); + } + } + } + + this.isPoolingEnabled = true; + String poolingEnabledParam + = config.getInitParameter("enablePooling"); + if (poolingEnabledParam != null + && !poolingEnabledParam.equalsIgnoreCase("true")) { + if (poolingEnabledParam.equalsIgnoreCase("false")) { + this.isPoolingEnabled = false; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.enablePooling")); + } + } + } + + String mapFile = config.getInitParameter("mappedfile"); + if (mapFile != null) { + if (mapFile.equalsIgnoreCase("true")) { + this.mappedFile = true; + } else if (mapFile.equalsIgnoreCase("false")) { + this.mappedFile = false; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.mappedFile")); + } + } + } + + String debugInfo = config.getInitParameter("classdebuginfo"); + if (debugInfo != null) { + if (debugInfo.equalsIgnoreCase("true")) { + this.classDebugInfo = true; + } else if (debugInfo.equalsIgnoreCase("false")) { + this.classDebugInfo = false; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.classDebugInfo")); + } + } + } + + String checkInterval = config.getInitParameter("checkInterval"); + if (checkInterval != null) { + try { + this.checkInterval = Integer.parseInt(checkInterval); + } catch(NumberFormatException ex) { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.checkInterval")); + } + } + } + + String modificationTestInterval = config.getInitParameter("modificationTestInterval"); + if (modificationTestInterval != null) { + try { + this.modificationTestInterval = Integer.parseInt(modificationTestInterval); + } catch(NumberFormatException ex) { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.modificationTestInterval")); + } + } + } + + String development = config.getInitParameter("development"); + if (development != null) { + if (development.equalsIgnoreCase("true")) { + this.development = true; + } else if (development.equalsIgnoreCase("false")) { + this.development = false; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.development")); + } + } + } + + String suppressSmap = config.getInitParameter("suppressSmap"); + if (suppressSmap != null) { + if (suppressSmap.equalsIgnoreCase("true")) { + isSmapSuppressed = true; + } else if (suppressSmap.equalsIgnoreCase("false")) { + isSmapSuppressed = false; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.suppressSmap")); + } + } + } + + String dumpSmap = config.getInitParameter("dumpSmap"); + if (dumpSmap != null) { + if (dumpSmap.equalsIgnoreCase("true")) { + isSmapDumped = true; + } else if (dumpSmap.equalsIgnoreCase("false")) { + isSmapDumped = false; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.dumpSmap")); + } + } + } + + String genCharArray = config.getInitParameter("genStrAsCharArray"); + if (genCharArray != null) { + if (genCharArray.equalsIgnoreCase("true")) { + genStringAsCharArray = true; + } else if (genCharArray.equalsIgnoreCase("false")) { + genStringAsCharArray = false; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.genchararray")); + } + } + } + + String errBeanClass = + config.getInitParameter("errorOnUseBeanInvalidClassAttribute"); + if (errBeanClass != null) { + if (errBeanClass.equalsIgnoreCase("true")) { + errorOnUseBeanInvalidClassAttribute = true; + } else if (errBeanClass.equalsIgnoreCase("false")) { + errorOnUseBeanInvalidClassAttribute = false; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.errBean")); + } + } + } + + String ieClassId = config.getInitParameter("ieClassId"); + if (ieClassId != null) + this.ieClassId = ieClassId; + + String classpath = config.getInitParameter("classpath"); + if (classpath != null) + this.classpath = classpath; + + /* + * scratchdir + */ + String dir = config.getInitParameter("scratchdir"); + if (dir != null) { + scratchDir = new File(dir); + } else { + // First try the Servlet 2.2 javax.servlet.context.tempdir property + scratchDir = (File) context.getAttribute(Constants.TMP_DIR); + if (scratchDir == null) { + // Not running in a Servlet 2.2 container. + // Try to get the JDK 1.2 java.io.tmpdir property + dir = System.getProperty("java.io.tmpdir"); + if (dir != null) + scratchDir = new File(dir); + } + } + if (this.scratchDir == null) { + log.fatal(Localizer.getMessage("jsp.error.no.scratch.dir")); + return; + } + + if (!(scratchDir.exists() && scratchDir.canRead() && + scratchDir.canWrite() && scratchDir.isDirectory())) + log.fatal(Localizer.getMessage("jsp.error.bad.scratch.dir", + scratchDir.getAbsolutePath())); + + this.compiler = config.getInitParameter("compiler"); + + String compilerTargetVM = config.getInitParameter("compilerTargetVM"); + if(compilerTargetVM != null) { + this.compilerTargetVM = compilerTargetVM; + } + + String compilerSourceVM = config.getInitParameter("compilerSourceVM"); + if(compilerSourceVM != null) { + this.compilerSourceVM = compilerSourceVM; + } + + String javaEncoding = config.getInitParameter("javaEncoding"); + if (javaEncoding != null) { + this.javaEncoding = javaEncoding; + } + + String compilerClassName = config.getInitParameter("compilerClassName"); + if (compilerClassName != null) { + this.compilerClassName = compilerClassName; + } + + String fork = config.getInitParameter("fork"); + if (fork != null) { + if (fork.equalsIgnoreCase("true")) { + this.fork = true; + } else if (fork.equalsIgnoreCase("false")) { + this.fork = false; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.fork")); + } + } + } + + String xpoweredBy = config.getInitParameter("xpoweredBy"); + if (xpoweredBy != null) { + if (xpoweredBy.equalsIgnoreCase("true")) { + this.xpoweredBy = true; + } else if (xpoweredBy.equalsIgnoreCase("false")) { + this.xpoweredBy = false; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.xpoweredBy")); + } + } + } + + String displaySourceFragment = config.getInitParameter("displaySourceFragment"); + if (displaySourceFragment != null) { + if (displaySourceFragment.equalsIgnoreCase("true")) { + this.displaySourceFragment = true; + } else if (displaySourceFragment.equalsIgnoreCase("false")) { + this.displaySourceFragment = false; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.displaySourceFragment")); + } + } + } + + // Setup the global Tag Libraries location cache for this + // web-application. + tldLocationsCache = new TldLocationsCache(context); + + // Setup the jsp config info for this web app. + jspConfig = new JspConfig(context); + + // Create a Tag plugin instance + tagPluginManager = new TagPluginManager(context); + } + +} + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/JasperException.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/JasperException.java new file mode 100644 index 000000000..cc06a2bf2 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/JasperException.java @@ -0,0 +1,46 @@ +/* + * 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; + +/** + * Base class for all exceptions generated by the JSP engine. Makes it + * convienient to catch just this at the top-level. + * + * @author Anil K. Vijendran + */ +public class JasperException extends javax.servlet.ServletException { + + public JasperException(String reason) { + super(reason); + } + + /** + * Creates a JasperException with the embedded exception and the reason for + * throwing a JasperException + */ + public JasperException (String reason, Throwable exception) { + super(reason, exception); + } + + /** + * Creates a JasperException with the embedded exception + */ + public JasperException (Throwable exception) { + super(exception); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/JspC.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/JspC.java new file mode 100644 index 000000000..e7e9dfd28 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/JspC.java @@ -0,0 +1,1424 @@ +/* + * 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; + +import java.io.BufferedReader; +import java.io.CharArrayWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Stack; +import java.util.StringTokenizer; +import java.util.Vector; + +import org.apache.jasper.compiler.Compiler; +import org.apache.jasper.compiler.JspConfig; +import org.apache.jasper.compiler.JspRuntimeContext; +import org.apache.jasper.compiler.Localizer; +import org.apache.jasper.compiler.TagPluginManager; +import org.apache.jasper.compiler.TldLocationsCache; +import org.apache.jasper.servlet.JspCServletContext; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.FileUtils; + +/** + * Shell for the jspc compiler. Handles all options associated with the + * command line and creates compilation contexts which it then compiles + * according to the specified options. + * + * This version can process files from a _single_ webapp at once, i.e. + * a single docbase can be specified. + * + * It can be used as an Ant task using: + *
+ *   <taskdef classname="org.apache.jasper.JspC" name="jasper2" >
+ *      <classpath>
+ *          <pathelement location="${java.home}/../lib/tools.jar"/>
+ *          <fileset dir="${ENV.CATALINA_HOME}/server/lib">
+ *              <include name="*.jar"/>
+ *          </fileset>
+ *          <fileset dir="${ENV.CATALINA_HOME}/common/lib">
+ *              <include name="*.jar"/>
+ *          </fileset>
+ *          <path refid="myjars"/>
+ *       </classpath>
+ *  </taskdef>
+ *
+ *  <jasper2 verbose="0"
+ *           package="my.package"
+ *           uriroot="${webapps.dir}/${webapp.name}"
+ *           webXmlFragment="${build.dir}/generated_web.xml"
+ *           outputDir="${webapp.dir}/${webapp.name}/WEB-INF/src/my/package" />
+ * 
+ * + * @author Danno Ferrin + * @author Pierre Delisle + * @author Costin Manolache + * @author Yoav Shapira + */ +public class JspC implements Options { + + public static final String DEFAULT_IE_CLASS_ID = + "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"; + + // Logger + protected static Log log = LogFactory.getLog(JspC.class); + + protected static final String SWITCH_VERBOSE = "-v"; + protected static final String SWITCH_HELP = "-help"; + protected static final String SWITCH_OUTPUT_DIR = "-d"; + protected static final String SWITCH_PACKAGE_NAME = "-p"; + protected static final String SWITCH_CACHE = "-cache"; + protected static final String SWITCH_CLASS_NAME = "-c"; + protected static final String SWITCH_FULL_STOP = "--"; + protected static final String SWITCH_COMPILE = "-compile"; + protected static final String SWITCH_SOURCE = "-source"; + protected static final String SWITCH_TARGET = "-target"; + protected static final String SWITCH_URI_BASE = "-uribase"; + protected static final String SWITCH_URI_ROOT = "-uriroot"; + protected static final String SWITCH_FILE_WEBAPP = "-webapp"; + protected static final String SWITCH_WEBAPP_INC = "-webinc"; + protected static final String SWITCH_WEBAPP_XML = "-webxml"; + protected static final String SWITCH_MAPPED = "-mapped"; + protected static final String SWITCH_XPOWERED_BY = "-xpoweredBy"; + protected static final String SWITCH_TRIM_SPACES = "-trimSpaces"; + protected static final String SWITCH_CLASSPATH = "-classpath"; + protected static final String SWITCH_DIE = "-die"; + protected static final String SWITCH_POOLING = "-poolingEnabled"; + protected static final String SWITCH_ENCODING = "-javaEncoding"; + protected static final String SWITCH_SMAP = "-smap"; + protected static final String SWITCH_DUMP_SMAP = "-dumpsmap"; + + protected static final String SHOW_SUCCESS ="-s"; + protected static final String LIST_ERRORS = "-l"; + protected static final int INC_WEBXML = 10; + protected static final int ALL_WEBXML = 20; + protected static final int DEFAULT_DIE_LEVEL = 1; + protected static final int NO_DIE_LEVEL = 0; + + protected static final String[] insertBefore = + { "", "", "", + "", "", "", "", + "", "", "", + "", "", "", "", + "" }; + + protected static int die; + protected String classPath = null; + protected URLClassLoader loader = null; + protected boolean trimSpaces = false; + protected boolean genStringAsCharArray = false; + protected boolean xpoweredBy; + protected boolean mappedFile = false; + protected boolean poolingEnabled = true; + protected File scratchDir; + protected String ieClassId = DEFAULT_IE_CLASS_ID; + protected String targetPackage; + protected String targetClassName; + protected String uriBase; + protected String uriRoot; + protected Project project; + protected int dieLevel; + protected boolean helpNeeded = false; + protected boolean compile = false; + protected boolean smapSuppressed = true; + protected boolean smapDumped = false; + protected boolean caching = true; + protected Map cache = new HashMap(); + + protected String compiler = null; + + protected String compilerTargetVM = "1.4"; + protected String compilerSourceVM = "1.4"; + + protected boolean classDebugInfo = true; + + /** + * Throw an exception if there's a compilation error, or swallow it. + * Default is true to preserve old behavior. + */ + protected boolean failOnError = true; + + /** + * The file extensions to be handled as JSP files. + * Default list is .jsp and .jspx. + */ + protected List extensions; + + /** + * The pages. + */ + protected List pages = new Vector(); + + /** + * Needs better documentation, this data member does. + * True by default. + */ + protected boolean errorOnUseBeanInvalidClassAttribute = true; + + /** + * The java file encoding. Default + * is UTF-8. Added per bugzilla 19622. + */ + protected String javaEncoding = "UTF-8"; + + // Generation of web.xml fragments + protected String webxmlFile; + protected int webxmlLevel; + protected boolean addWebXmlMappings = false; + + protected Writer mapout; + protected CharArrayWriter servletout; + protected CharArrayWriter mappingout; + + /** + * The servlet context. + */ + protected JspCServletContext context; + + /** + * The runtime context. + * Maintain a dummy JspRuntimeContext for compiling tag files. + */ + protected JspRuntimeContext rctxt; + + /** + * Cache for the TLD locations + */ + protected TldLocationsCache tldLocationsCache = null; + + protected JspConfig jspConfig = null; + protected TagPluginManager tagPluginManager = null; + + protected boolean verbose = false; + protected boolean listErrors = false; + protected boolean showSuccess = false; + protected int argPos; + protected boolean fullstop = false; + protected String args[]; + + public static void main(String arg[]) { + if (arg.length == 0) { + System.out.println(Localizer.getMessage("jspc.usage")); + } else { + try { + JspC jspc = new JspC(); + jspc.setArgs(arg); + if (jspc.helpNeeded) { + System.out.println(Localizer.getMessage("jspc.usage")); + } else { + jspc.execute(); + } + } catch (JasperException je) { + System.err.println(je); + if (die != NO_DIE_LEVEL) { + System.exit(die); + } + } + } + } + + public void setArgs(String[] arg) throws JasperException { + args = arg; + String tok; + + dieLevel = NO_DIE_LEVEL; + die = dieLevel; + + while ((tok = nextArg()) != null) { + if (tok.equals(SWITCH_VERBOSE)) { + verbose = true; + showSuccess = true; + listErrors = true; + } else if (tok.equals(SWITCH_OUTPUT_DIR)) { + tok = nextArg(); + setOutputDir( tok ); + } else if (tok.equals(SWITCH_PACKAGE_NAME)) { + targetPackage = nextArg(); + } else if (tok.equals(SWITCH_COMPILE)) { + compile=true; + } else if (tok.equals(SWITCH_CLASS_NAME)) { + targetClassName = nextArg(); + } else if (tok.equals(SWITCH_URI_BASE)) { + uriBase=nextArg(); + } else if (tok.equals(SWITCH_URI_ROOT)) { + setUriroot( nextArg()); + } else if (tok.equals(SWITCH_FILE_WEBAPP)) { + setUriroot( nextArg()); + } else if ( tok.equals( SHOW_SUCCESS ) ) { + showSuccess = true; + } else if ( tok.equals( LIST_ERRORS ) ) { + listErrors = true; + } else if (tok.equals(SWITCH_WEBAPP_INC)) { + webxmlFile = nextArg(); + if (webxmlFile != null) { + webxmlLevel = INC_WEBXML; + } + } else if (tok.equals(SWITCH_WEBAPP_XML)) { + webxmlFile = nextArg(); + if (webxmlFile != null) { + webxmlLevel = ALL_WEBXML; + } + } else if (tok.equals(SWITCH_MAPPED)) { + mappedFile = true; + } else if (tok.equals(SWITCH_XPOWERED_BY)) { + xpoweredBy = true; + } else if (tok.equals(SWITCH_TRIM_SPACES)) { + setTrimSpaces(true); + } else if (tok.equals(SWITCH_CACHE)) { + tok = nextArg(); + if ("false".equals(tok)) { + caching = false; + } else { + caching = true; + } + } else if (tok.equals(SWITCH_CLASSPATH)) { + setClassPath(nextArg()); + } else if (tok.startsWith(SWITCH_DIE)) { + try { + dieLevel = Integer.parseInt( + tok.substring(SWITCH_DIE.length())); + } catch (NumberFormatException nfe) { + dieLevel = DEFAULT_DIE_LEVEL; + } + die = dieLevel; + } else if (tok.equals(SWITCH_HELP)) { + helpNeeded = true; + } else if (tok.equals(SWITCH_POOLING)) { + tok = nextArg(); + if ("false".equals(tok)) { + poolingEnabled = false; + } else { + poolingEnabled = true; + } + } else if (tok.equals(SWITCH_ENCODING)) { + setJavaEncoding(nextArg()); + } else if (tok.equals(SWITCH_SOURCE)) { + setCompilerSourceVM(nextArg()); + } else if (tok.equals(SWITCH_TARGET)) { + setCompilerTargetVM(nextArg()); + } else if (tok.equals(SWITCH_SMAP)) { + smapSuppressed = false; + } else if (tok.equals(SWITCH_DUMP_SMAP)) { + smapDumped = true; + } else { + if (tok.startsWith("-")) { + throw new JasperException("Unrecognized option: " + tok + + ". Use -help for help."); + } + if (!fullstop) { + argPos--; + } + // Start treating the rest as JSP Pages + break; + } + } + + // Add all extra arguments to the list of files + while( true ) { + String file = nextFile(); + if( file==null ) { + break; + } + pages.add( file ); + } + } + + public boolean getKeepGenerated() { + // isn't this why we are running jspc? + return true; + } + + public boolean getTrimSpaces() { + return trimSpaces; + } + + public void setTrimSpaces(boolean ts) { + this.trimSpaces = ts; + } + + public boolean isPoolingEnabled() { + return poolingEnabled; + } + + public void setPoolingEnabled(boolean poolingEnabled) { + this.poolingEnabled = poolingEnabled; + } + + public boolean isXpoweredBy() { + return xpoweredBy; + } + + public void setXpoweredBy(boolean xpoweredBy) { + this.xpoweredBy = xpoweredBy; + } + + public boolean getDisplaySourceFragment() { + return true; + } + + public boolean getErrorOnUseBeanInvalidClassAttribute() { + return errorOnUseBeanInvalidClassAttribute; + } + + public void setErrorOnUseBeanInvalidClassAttribute(boolean b) { + errorOnUseBeanInvalidClassAttribute = b; + } + + public int getTagPoolSize() { + return Constants.MAX_POOL_SIZE; + } + + /** + * Are we supporting HTML mapped servlets? + */ + public boolean getMappedFile() { + return mappedFile; + } + + // Off-line compiler, no need for security manager + public Object getProtectionDomain() { + return null; + } + + /** + * @deprecated + */ + @Deprecated + public boolean getSendErrorToClient() { + return true; + } + + public void setClassDebugInfo( boolean b ) { + classDebugInfo=b; + } + + public boolean getClassDebugInfo() { + // compile with debug info + return classDebugInfo; + } + + /** + * @see Options#isCaching() + */ + public boolean isCaching() { + return caching; + } + + /** + * @see Options#isCaching() + */ + public void setCaching(boolean caching) { + this.caching = caching; + } + + /** + * @see Options#getCache() + */ + public Map getCache() { + return cache; + } + + /** + * Background compilation check intervals in seconds + */ + public int getCheckInterval() { + return 0; + } + + /** + * Modification test interval. + */ + public int getModificationTestInterval() { + return 0; + } + + /** + * Is Jasper being used in development mode? + */ + public boolean getDevelopment() { + return false; + } + + /** + * Is the generation of SMAP info for JSR45 debuggin suppressed? + */ + public boolean isSmapSuppressed() { + return smapSuppressed; + } + + /** + * Set smapSuppressed flag. + */ + public void setSmapSuppressed(boolean smapSuppressed) { + this.smapSuppressed = smapSuppressed; + } + + + /** + * Should SMAP info for JSR45 debugging be dumped to a file? + */ + public boolean isSmapDumped() { + return smapDumped; + } + + /** + * Set smapSuppressed flag. + */ + public void setSmapDumped(boolean smapDumped) { + this.smapDumped = smapDumped; + } + + + /** + * Determines whether text strings are to be generated as char arrays, + * which improves performance in some cases. + * + * @param genStringAsCharArray true if text strings are to be generated as + * char arrays, false otherwise + */ + public void setGenStringAsCharArray(boolean genStringAsCharArray) { + this.genStringAsCharArray = genStringAsCharArray; + } + + /** + * Indicates whether text strings are to be generated as char arrays. + * + * @return true if text strings are to be generated as char arrays, false + * otherwise + */ + public boolean genStringAsCharArray() { + return genStringAsCharArray; + } + + /** + * Sets the class-id value to be sent to Internet Explorer when using + * tags. + * + * @param ieClassId Class-id value + */ + public void setIeClassId(String ieClassId) { + this.ieClassId = ieClassId; + } + + /** + * Gets the class-id value that is sent to Internet Explorer when using + * tags. + * + * @return Class-id value + */ + public String getIeClassId() { + return ieClassId; + } + + public File getScratchDir() { + return scratchDir; + } + + public Class getJspCompilerPlugin() { + // we don't compile, so this is meanlingless + return null; + } + + public String getJspCompilerPath() { + // we don't compile, so this is meanlingless + return null; + } + + /** + * Compiler to use. + */ + public String getCompiler() { + return compiler; + } + + public void setCompiler(String c) { + compiler=c; + } + + /** + * Compiler class name to use. + */ + public String getCompilerClassName() { + return null; + } + + /** + * @see Options#getCompilerTargetVM + */ + public String getCompilerTargetVM() { + return compilerTargetVM; + } + + public void setCompilerTargetVM(String vm) { + compilerTargetVM = vm; + } + + /** + * @see Options#getCompilerSourceVM() + */ + public String getCompilerSourceVM() { + return compilerSourceVM; + } + + /** + * @see Options#getCompilerSourceVM() + */ + public void setCompilerSourceVM(String vm) { + compilerSourceVM = vm; + } + + public TldLocationsCache getTldLocationsCache() { + return tldLocationsCache; + } + + /** + * Returns the encoding to use for + * java files. The default is UTF-8. + * + * @return String The encoding + */ + public String getJavaEncoding() { + return javaEncoding; + } + + /** + * Sets the encoding to use for + * java files. + * + * @param encodingName The name, e.g. "UTF-8" + */ + public void setJavaEncoding(String encodingName) { + javaEncoding = encodingName; + } + + public boolean getFork() { + return false; + } + + public String getClassPath() { + if( classPath != null ) + return classPath; + return System.getProperty("java.class.path"); + } + + public void setClassPath(String s) { + classPath=s; + } + + /** + * Returns the list of file extensions + * that are treated as JSP files. + * + * @return The list of extensions + */ + public List getExtensions() { + return extensions; + } + + /** + * Adds the given file extension to the + * list of extensions handled as JSP files. + * + * @param extension The extension to add, e.g. "myjsp" + */ + protected void addExtension(final String extension) { + if(extension != null) { + if(extensions == null) { + extensions = new Vector(); + } + + extensions.add(extension); + } + } + + /** + * Sets the project. + * + * @param theProject The project + */ + public void setProject(final Project theProject) { + project = theProject; + } + + /** + * Returns the project: may be null if not running + * inside an Ant project. + * + * @return The project + */ + public Project getProject() { + return project; + } + + /** + * Base dir for the webapp. Used to generate class names and resolve + * includes + */ + public void setUriroot( String s ) { + if( s==null ) { + uriRoot = s; + return; + } + try { + uriRoot = resolveFile(s).getCanonicalPath(); + } catch( Exception ex ) { + uriRoot = s; + } + } + + /** + * Parses comma-separated list of JSP files to be processed. If the argument + * is null, nothing is done. + * + *

Each file is interpreted relative to uriroot, unless it is absolute, + * in which case it must start with uriroot.

+ * + * @param jspFiles Comma-separated list of JSP files to be processed + */ + public void setJspFiles(final String jspFiles) { + if(jspFiles == null) { + return; + } + + StringTokenizer tok = new StringTokenizer(jspFiles, ","); + while (tok.hasMoreTokens()) { + pages.add(tok.nextToken()); + } + } + + /** + * Sets the compile flag. + * + * @param b Flag value + */ + public void setCompile( final boolean b ) { + compile = b; + } + + /** + * Sets the verbosity level. The actual number doesn't + * matter: if it's greater than zero, the verbose flag will + * be true. + * + * @param level Positive means verbose + */ + public void setVerbose( final int level ) { + if (level > 0) { + verbose = true; + showSuccess = true; + listErrors = true; + } + } + + public void setValidateXml( boolean b ) { + org.apache.jasper.xmlparser.ParserUtils.validating=b; + } + + public void setListErrors( boolean b ) { + listErrors = b; + } + + public void setOutputDir( String s ) { + if( s!= null ) { + scratchDir = resolveFile(s).getAbsoluteFile(); + } else { + scratchDir=null; + } + } + + public void setPackage( String p ) { + targetPackage=p; + } + + /** + * Class name of the generated file ( without package ). + * Can only be used if a single file is converted. + * XXX Do we need this feature ? + */ + public void setClassName( String p ) { + targetClassName=p; + } + + /** + * File where we generate a web.xml fragment with the class definitions. + */ + public void setWebXmlFragment( String s ) { + webxmlFile=resolveFile(s).getAbsolutePath(); + webxmlLevel=INC_WEBXML; + } + + /** + * File where we generate a complete web.xml with the class definitions. + */ + public void setWebXml( String s ) { + webxmlFile=resolveFile(s).getAbsolutePath(); + webxmlLevel=ALL_WEBXML; + } + + public void setAddWebXmlMappings(boolean b) { + addWebXmlMappings = b; + } + + /** + * Set the option that throws an exception in case of a compilation error. + */ + public void setFailOnError(final boolean b) { + failOnError = b; + } + + public boolean getFailOnError() { + return failOnError; + } + + /** + * Obtain JSP configuration informantion specified in web.xml. + */ + public JspConfig getJspConfig() { + return jspConfig; + } + + public TagPluginManager getTagPluginManager() { + return tagPluginManager; + } + + public void generateWebMapping( String file, JspCompilationContext clctxt ) + throws IOException + { + if (log.isDebugEnabled()) { + log.debug("Generating web mapping for file " + file + + " using compilation context " + clctxt); + } + + String className = clctxt.getServletClassName(); + String packageName = clctxt.getServletPackageName(); + + String thisServletName; + if ("".equals(packageName)) { + thisServletName = className; + } else { + thisServletName = packageName + '.' + className; + } + + if (servletout != null) { + servletout.write("\n \n "); + servletout.write(thisServletName); + servletout.write("\n "); + servletout.write(thisServletName); + servletout.write("\n \n"); + } + if (mappingout != null) { + mappingout.write("\n \n "); + mappingout.write(thisServletName); + mappingout.write("\n "); + mappingout.write(file.replace('\\', '/')); + mappingout.write("\n \n"); + + } + } + + /** + * Include the generated web.xml inside the webapp's web.xml. + */ + protected void mergeIntoWebXml() throws IOException { + + File webappBase = new File(uriRoot); + File webXml = new File(webappBase, "WEB-INF/web.xml"); + File webXml2 = new File(webappBase, "WEB-INF/web2.xml"); + String insertStartMarker = + Localizer.getMessage("jspc.webinc.insertStart"); + String insertEndMarker = + Localizer.getMessage("jspc.webinc.insertEnd"); + + BufferedReader reader = new BufferedReader(new FileReader(webXml)); + BufferedReader fragmentReader = + new BufferedReader(new FileReader(webxmlFile)); + PrintWriter writer = new PrintWriter(new FileWriter(webXml2)); + + // Insert the and declarations + int pos = -1; + String line = null; + while (true) { + line = reader.readLine(); + if (line == null) { + break; + } + // Skip anything previously generated by JSPC + if (line.indexOf(insertStartMarker) >= 0) { + while (true) { + line = reader.readLine(); + if (line == null) { + return; + } + if (line.indexOf(insertEndMarker) >= 0) { + line = reader.readLine(); + line = reader.readLine(); + if (line == null) { + return; + } + break; + } + } + } + for (int i = 0; i < insertBefore.length; i++) { + pos = line.indexOf(insertBefore[i]); + if (pos >= 0) + break; + } + if (pos >= 0) { + writer.print(line.substring(0, pos)); + break; + } else { + writer.println(line); + } + } + + writer.println(insertStartMarker); + while (true) { + String line2 = fragmentReader.readLine(); + if (line2 == null) { + writer.println(); + break; + } + writer.println(line2); + } + writer.println(insertEndMarker); + writer.println(); + + for (int i = 0; i < pos; i++) { + writer.print(" "); + } + writer.println(line.substring(pos)); + + while (true) { + line = reader.readLine(); + if (line == null) { + break; + } + writer.println(line); + } + writer.close(); + + reader.close(); + fragmentReader.close(); + + FileInputStream fis = new FileInputStream(webXml2); + FileOutputStream fos = new FileOutputStream(webXml); + + byte buf[] = new byte[512]; + while (true) { + int n = fis.read(buf); + if (n < 0) { + break; + } + fos.write(buf, 0, n); + } + + fis.close(); + fos.close(); + + webXml2.delete(); + (new File(webxmlFile)).delete(); + + } + + protected void processFile(String file) + throws JasperException + { + if (log.isDebugEnabled()) { + log.debug("Processing file: " + file); + } + + ClassLoader originalClassLoader = null; + + try { + // set up a scratch/output dir if none is provided + if (scratchDir == null) { + String temp = System.getProperty("java.io.tmpdir"); + if (temp == null) { + temp = ""; + } + scratchDir = new File(new File(temp).getAbsolutePath()); + } + + String jspUri=file.replace('\\','/'); + JspCompilationContext clctxt = new JspCompilationContext + ( jspUri, false, this, context, null, rctxt ); + + /* Override the defaults */ + if ((targetClassName != null) && (targetClassName.length() > 0)) { + clctxt.setServletClassName(targetClassName); + targetClassName = null; + } + if (targetPackage != null) { + clctxt.setServletPackageName(targetPackage); + } + + originalClassLoader = Thread.currentThread().getContextClassLoader(); + if( loader==null ) { + initClassLoader( clctxt ); + } + Thread.currentThread().setContextClassLoader(loader); + + clctxt.setClassLoader(loader); + clctxt.setClassPath(classPath); + + Compiler clc = clctxt.createCompiler(); + + // If compile is set, generate both .java and .class, if + // .jsp file is newer than .class file; + // Otherwise only generate .java, if .jsp file is newer than + // the .java file + if( clc.isOutDated(compile) ) { + if (log.isDebugEnabled()) { + log.debug(jspUri + " is out dated, compiling..."); + } + + clc.compile(compile, true); + } + + // Generate mapping + generateWebMapping( file, clctxt ); + if ( showSuccess ) { + log.info( "Built File: " + file ); + } + + } catch (JasperException je) { + Throwable rootCause = je; + while (rootCause instanceof JasperException + && ((JasperException) rootCause).getRootCause() != null) { + rootCause = ((JasperException) rootCause).getRootCause(); + } + if (rootCause != je) { + log.error(Localizer.getMessage("jspc.error.generalException", + file), + rootCause); + } + + // Bugzilla 35114. + if(getFailOnError()) { + throw je; + } else { + log.error(je.getMessage()); + } + + } catch (Exception e) { + if ((e instanceof FileNotFoundException) && log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jspc.error.fileDoesNotExist", + e.getMessage())); + } + throw new JasperException(e); + } finally { + if(originalClassLoader != null) { + Thread.currentThread().setContextClassLoader(originalClassLoader); + } + } + } + + /** + * Locate all jsp files in the webapp. Used if no explicit + * jsps are specified. + */ + public void scanFiles( File base ) throws JasperException { + Stack dirs = new Stack(); + dirs.push(base.toString()); + + // Make sure default extensions are always included + if ((getExtensions() == null) || (getExtensions().size() < 2)) { + addExtension("jsp"); + addExtension("jspx"); + } + + while (!dirs.isEmpty()) { + String s = dirs.pop(); + File f = new File(s); + if (f.exists() && f.isDirectory()) { + String[] files = f.list(); + String ext; + for (int i = 0; (files != null) && i < files.length; i++) { + File f2 = new File(s, files[i]); + if (f2.isDirectory()) { + dirs.push(f2.getPath()); + } else { + String path = f2.getPath(); + String uri = path.substring(uriRoot.length()); + ext = files[i].substring(files[i].lastIndexOf('.') +1); + if (getExtensions().contains(ext) || + jspConfig.isJspPage(uri)) { + pages.add(path); + } + } + } + } + } + } + + /** + * Executes the compilation. + * + * @throws JasperException If an error occurs + */ + public void execute() throws JasperException { + if(log.isDebugEnabled()) { + log.debug("execute() starting for " + pages.size() + " pages."); + } + + try { + if (uriRoot == null) { + if( pages.size() == 0 ) { + throw new JasperException( + Localizer.getMessage("jsp.error.jspc.missingTarget")); + } + String firstJsp = (String) pages.get( 0 ); + File firstJspF = new File( firstJsp ); + if (!firstJspF.exists()) { + throw new JasperException( + Localizer.getMessage("jspc.error.fileDoesNotExist", + firstJsp)); + } + locateUriRoot( firstJspF ); + } + + if (uriRoot == null) { + throw new JasperException( + Localizer.getMessage("jsp.error.jspc.no_uriroot")); + } + + if( context==null ) { + initServletContext(); + } + + // No explicit pages, we'll process all .jsp in the webapp + if (pages.size() == 0) { + scanFiles( new File( uriRoot )); + } + + File uriRootF = new File(uriRoot); + if (!uriRootF.exists() || !uriRootF.isDirectory()) { + throw new JasperException( + Localizer.getMessage("jsp.error.jspc.uriroot_not_dir")); + } + + initWebXml(); + + Iterator iter = pages.iterator(); + while (iter.hasNext()) { + String nextjsp = iter.next().toString(); + File fjsp = new File(nextjsp); + if (!fjsp.isAbsolute()) { + fjsp = new File(uriRootF, nextjsp); + } + if (!fjsp.exists()) { + if (log.isWarnEnabled()) { + log.warn + (Localizer.getMessage + ("jspc.error.fileDoesNotExist", fjsp.toString())); + } + continue; + } + String s = fjsp.getAbsolutePath(); + if (s.startsWith(uriRoot)) { + nextjsp = s.substring(uriRoot.length()); + } + if (nextjsp.startsWith("." + File.separatorChar)) { + nextjsp = nextjsp.substring(2); + } + processFile(nextjsp); + } + + completeWebXml(); + + if (addWebXmlMappings) { + mergeIntoWebXml(); + } + + } catch (IOException ioe) { + throw new JasperException(ioe); + + } catch (JasperException je) { + Throwable rootCause = je; + while (rootCause instanceof JasperException + && ((JasperException) rootCause).getRootCause() != null) { + rootCause = ((JasperException) rootCause).getRootCause(); + } + if (rootCause != je) { + rootCause.printStackTrace(); + } + throw je; + } finally { + if (loader != null) { + LogFactory.release(loader); + } + } + } + + // ==================== protected utility methods ==================== + + protected String nextArg() { + if ((argPos >= args.length) + || (fullstop = SWITCH_FULL_STOP.equals(args[argPos]))) { + return null; + } else { + return args[argPos++]; + } + } + + protected String nextFile() { + if (fullstop) argPos++; + if (argPos >= args.length) { + return null; + } else { + return args[argPos++]; + } + } + + protected void initWebXml() { + try { + if (webxmlLevel >= INC_WEBXML) { + File fmapings = new File(webxmlFile); + mapout = new FileWriter(fmapings); + servletout = new CharArrayWriter(); + mappingout = new CharArrayWriter(); + } else { + mapout = null; + servletout = null; + mappingout = null; + } + if (webxmlLevel >= ALL_WEBXML) { + mapout.write(Localizer.getMessage("jspc.webxml.header")); + mapout.flush(); + } else if ((webxmlLevel>= INC_WEBXML) && !addWebXmlMappings) { + mapout.write(Localizer.getMessage("jspc.webinc.header")); + mapout.flush(); + } + } catch (IOException ioe) { + mapout = null; + servletout = null; + mappingout = null; + } + } + + protected void completeWebXml() { + if (mapout != null) { + try { + servletout.writeTo(mapout); + mappingout.writeTo(mapout); + if (webxmlLevel >= ALL_WEBXML) { + mapout.write(Localizer.getMessage("jspc.webxml.footer")); + } else if ((webxmlLevel >= INC_WEBXML) && !addWebXmlMappings) { + mapout.write(Localizer.getMessage("jspc.webinc.footer")); + } + mapout.close(); + } catch (IOException ioe) { + // noting to do if it fails since we are done with it + } + } + } + + protected void initServletContext() { + try { + context =new JspCServletContext + (new PrintWriter(System.out), + new URL("file:" + uriRoot.replace('\\','/') + '/')); + tldLocationsCache = new TldLocationsCache(context, true); + } catch (MalformedURLException me) { + System.out.println("**" + me); + } + rctxt = new JspRuntimeContext(context, this); + jspConfig = new JspConfig(context); + tagPluginManager = new TagPluginManager(context); + } + + /** + * Initializes the classloader as/if needed for the given + * compilation context. + * + * @param clctxt The compilation context + * @throws IOException If an error occurs + */ + protected void initClassLoader(JspCompilationContext clctxt) + throws IOException { + + classPath = getClassPath(); + + ClassLoader jspcLoader = getClass().getClassLoader(); + if (jspcLoader instanceof AntClassLoader) { + classPath += File.pathSeparator + + ((AntClassLoader) jspcLoader).getClasspath(); + } + + // Turn the classPath into URLs + ArrayList urls = new ArrayList(); + StringTokenizer tokenizer = new StringTokenizer(classPath, + File.pathSeparator); + while (tokenizer.hasMoreTokens()) { + String path = tokenizer.nextToken(); + try { + File libFile = new File(path); + urls.add(libFile.toURL()); + } catch (IOException ioe) { + // Failing a toCanonicalPath on a file that + // exists() should be a JVM regression test, + // therefore we have permission to freak uot + throw new RuntimeException(ioe.toString()); + } + } + + File webappBase = new File(uriRoot); + if (webappBase.exists()) { + File classes = new File(webappBase, "/WEB-INF/classes"); + try { + if (classes.exists()) { + classPath = classPath + File.pathSeparator + + classes.getCanonicalPath(); + urls.add(classes.getCanonicalFile().toURL()); + } + } catch (IOException ioe) { + // failing a toCanonicalPath on a file that + // exists() should be a JVM regression test, + // therefore we have permission to freak out + throw new RuntimeException(ioe.toString()); + } + File lib = new File(webappBase, "/WEB-INF/lib"); + if (lib.exists() && lib.isDirectory()) { + String[] libs = lib.list(); + for (int i = 0; i < libs.length; i++) { + if( libs[i].length() <5 ) continue; + String ext=libs[i].substring( libs[i].length() - 4 ); + if (! ".jar".equalsIgnoreCase(ext)) { + if (".tld".equalsIgnoreCase(ext)) { + log.warn("TLD files should not be placed in " + + "/WEB-INF/lib"); + } + continue; + } + try { + File libFile = new File(lib, libs[i]); + classPath = classPath + File.pathSeparator + + libFile.getAbsolutePath(); + urls.add(libFile.getAbsoluteFile().toURL()); + } catch (IOException ioe) { + // failing a toCanonicalPath on a file that + // exists() should be a JVM regression test, + // therefore we have permission to freak out + throw new RuntimeException(ioe.toString()); + } + } + } + } + + // What is this ?? + urls.add(new File(clctxt.getRealPath("/")).getCanonicalFile().toURL()); + + URL urlsA[]=new URL[urls.size()]; + urls.toArray(urlsA); + loader = new URLClassLoader(urlsA, this.getClass().getClassLoader()); + + } + + /** + * Find the WEB-INF dir by looking up in the directory tree. + * This is used if no explicit docbase is set, but only files. + * XXX Maybe we should require the docbase. + */ + protected void locateUriRoot( File f ) { + String tUriBase = uriBase; + if (tUriBase == null) { + tUriBase = "/"; + } + try { + if (f.exists()) { + f = new File(f.getAbsolutePath()); + while (f != null) { + File g = new File(f, "WEB-INF"); + if (g.exists() && g.isDirectory()) { + uriRoot = f.getCanonicalPath(); + uriBase = tUriBase; + if (log.isInfoEnabled()) { + log.info(Localizer.getMessage( + "jspc.implicit.uriRoot", + uriRoot)); + } + break; + } + if (f.exists() && f.isDirectory()) { + tUriBase = "/" + f.getName() + "/" + tUriBase; + } + + String fParent = f.getParent(); + if (fParent == null) { + break; + } else { + f = new File(fParent); + } + + // If there is no acceptible candidate, uriRoot will + // remain null to indicate to the CompilerContext to + // use the current working/user dir. + } + + if (uriRoot != null) { + File froot = new File(uriRoot); + uriRoot = froot.getCanonicalPath(); + } + } + } catch (IOException ioe) { + // since this is an optional default and a null value + // for uriRoot has a non-error meaning, we can just + // pass straight through + } + } + + /** + * Resolves the relative or absolute pathname correctly + * in both Ant and command-line situations. If Ant launched + * us, we should use the basedir of the current project + * to resolve relative paths. + * + * See Bugzilla 35571. + * + * @param s The file + * @return The file resolved + */ + protected File resolveFile(final String s) { + if(getProject() == null) { + // Note FileUtils.getFileUtils replaces FileUtils.newFileUtils in Ant 1.6.3 + return FileUtils.newFileUtils().resolveFile(null, s); + } else { + return FileUtils.newFileUtils().resolveFile(getProject().getBaseDir(), s); + } + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/JspCompilationContext.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/JspCompilationContext.java new file mode 100644 index 000000000..813cf9bf2 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/JspCompilationContext.java @@ -0,0 +1,738 @@ +/* + * 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; + +import java.io.File; +import java.io.FileNotFoundException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletContext; +import javax.servlet.jsp.tagext.TagInfo; + +import org.apache.jasper.compiler.Compiler; +import org.apache.jasper.compiler.JspRuntimeContext; +import org.apache.jasper.compiler.JspUtil; +import org.apache.jasper.compiler.Localizer; +import org.apache.jasper.compiler.ServletWriter; +import org.apache.jasper.servlet.JasperLoader; +import org.apache.jasper.servlet.JspServletWrapper; + +/** + * A place holder for various things that are used through out the JSP + * engine. This is a per-request/per-context data structure. Some of + * the instance variables are set at different points. + * + * Most of the path-related stuff is here - mangling names, versions, dirs, + * loading resources and dealing with uris. + * + * @author Anil K. Vijendran + * @author Harish Prabandham + * @author Pierre Delisle + * @author Costin Manolache + * @author Kin-man Chung + */ +public class JspCompilationContext { + + protected org.apache.juli.logging.Log log = + org.apache.juli.logging.LogFactory.getLog(JspCompilationContext.class); + + protected Map tagFileJarUrls; + protected boolean isPackagedTagFile; + + protected String className; + protected String jspUri; + protected boolean isErrPage; + protected String basePackageName; + protected String derivedPackageName; + protected String servletJavaFileName; + protected String javaPath; + protected String classFileName; + protected String contentType; + protected ServletWriter writer; + protected Options options; + protected JspServletWrapper jsw; + protected Compiler jspCompiler; + protected String classPath; + + protected String baseURI; + protected String outputDir; + protected ServletContext context; + protected URLClassLoader loader; + + protected JspRuntimeContext rctxt; + + protected int removed = 0; + + protected URLClassLoader jspLoader; + protected URL baseUrl; + protected Class servletClass; + + protected boolean isTagFile; + protected boolean protoTypeMode; + protected TagInfo tagInfo; + protected URL tagFileJarUrl; + + // jspURI _must_ be relative to the context + public JspCompilationContext(String jspUri, + boolean isErrPage, + Options options, + ServletContext context, + JspServletWrapper jsw, + JspRuntimeContext rctxt) { + + this.jspUri = canonicalURI(jspUri); + this.isErrPage = isErrPage; + this.options = options; + this.jsw = jsw; + this.context = context; + + this.baseURI = jspUri.substring(0, jspUri.lastIndexOf('/') + 1); + // hack fix for resolveRelativeURI + if (baseURI == null) { + baseURI = "/"; + } else if (baseURI.charAt(0) != '/') { + // strip the basde slash since it will be combined with the + // uriBase to generate a file + baseURI = "/" + baseURI; + } + if (baseURI.charAt(baseURI.length() - 1) != '/') { + baseURI += '/'; + } + + this.rctxt = rctxt; + this.tagFileJarUrls = new HashMap(); + this.basePackageName = Constants.JSP_PACKAGE_NAME; + } + + public JspCompilationContext(String tagfile, + TagInfo tagInfo, + Options options, + ServletContext context, + JspServletWrapper jsw, + JspRuntimeContext rctxt, + URL tagFileJarUrl) { + this(tagfile, false, options, context, jsw, rctxt); + this.isTagFile = true; + this.tagInfo = tagInfo; + this.tagFileJarUrl = tagFileJarUrl; + if (tagFileJarUrl != null) { + isPackagedTagFile = true; + } + } + + /* ==================== Methods to override ==================== */ + + /** ---------- Class path and loader ---------- */ + + /** + * The classpath that is passed off to the Java compiler. + */ + public String getClassPath() { + if( classPath != null ) + return classPath; + return rctxt.getClassPath(); + } + + /** + * The classpath that is passed off to the Java compiler. + */ + public void setClassPath(String classPath) { + this.classPath = classPath; + } + + /** + * What class loader to use for loading classes while compiling + * this JSP? + */ + public ClassLoader getClassLoader() { + if( loader != null ) + return loader; + return rctxt.getParentClassLoader(); + } + + public void setClassLoader(URLClassLoader loader) { + this.loader = loader; + } + + public ClassLoader getJspLoader() { + if( jspLoader == null ) { + jspLoader = new JasperLoader + (new URL[] {baseUrl}, + getClassLoader(), + rctxt.getPermissionCollection(), + rctxt.getCodeSource()); + } + return jspLoader; + } + + /** ---------- Input/Output ---------- */ + + /** + * The output directory to generate code into. The output directory + * is make up of the scratch directory, which is provide in Options, + * plus the directory derived from the package name. + */ + public String getOutputDir() { + if (outputDir == null) { + createOutputDir(); + } + + return outputDir; + } + + /** + * Create a "Compiler" object based on some init param data. This + * is not done yet. Right now we're just hardcoding the actual + * compilers that are created. + */ + public Compiler createCompiler() throws JasperException { + if (jspCompiler != null ) { + return jspCompiler; + } + jspCompiler = null; + if (options.getCompilerClassName() != null) { + jspCompiler = createCompiler(options.getCompilerClassName()); + } else { + if (options.getCompiler() == null) { + jspCompiler = createCompiler("org.apache.jasper.compiler.JDTCompiler"); + if (jspCompiler == null) { + jspCompiler = createCompiler("org.apache.jasper.compiler.AntCompiler"); + } + } else { + jspCompiler = createCompiler("org.apache.jasper.compiler.AntCompiler"); + if (jspCompiler == null) { + jspCompiler = createCompiler("org.apache.jasper.compiler.JDTCompiler"); + } + } + } + if (jspCompiler == null) { + throw new IllegalStateException(Localizer.getMessage("jsp.error.compiler")); + } + jspCompiler.init(this, jsw); + return jspCompiler; + } + + protected Compiler createCompiler(String className) { + Compiler compiler = null; + try { + compiler = (Compiler) Class.forName(className).newInstance(); + } catch (InstantiationException e) { + log.warn(Localizer.getMessage("jsp.error.compiler"), e); + } catch (IllegalAccessException e) { + log.warn(Localizer.getMessage("jsp.error.compiler"), e); + } catch (NoClassDefFoundError e) { + if (log.isDebugEnabled()) { + log.debug(Localizer.getMessage("jsp.error.compiler"), e); + } + } catch (ClassNotFoundException e) { + if (log.isDebugEnabled()) { + log.debug(Localizer.getMessage("jsp.error.compiler"), e); + } + } + return compiler; + } + + public Compiler getCompiler() { + return jspCompiler; + } + + /** ---------- Access resources in the webapp ---------- */ + + /** + * Get the full value of a URI relative to this compilations context + * uses current file as the base. + */ + public String resolveRelativeUri(String uri) { + // sometimes we get uri's massaged from File(String), so check for + // a root directory deperator char + if (uri.startsWith("/") || uri.startsWith(File.separator)) { + return uri; + } else { + return baseURI + uri; + } + } + + /** + * Gets a resource as a stream, relative to the meanings of this + * context's implementation. + * @return a null if the resource cannot be found or represented + * as an InputStream. + */ + public java.io.InputStream getResourceAsStream(String res) { + return context.getResourceAsStream(canonicalURI(res)); + } + + + public URL getResource(String res) throws MalformedURLException { + URL result = null; + + if (res.startsWith("/META-INF/")) { + // This is a tag file packaged in a jar that is being compiled + URL jarUrl = tagFileJarUrls.get(res); + if (jarUrl == null) { + jarUrl = tagFileJarUrl; + } + if (jarUrl != null) { + result = new URL(jarUrl.toExternalForm() + res.substring(1)); + } + } else if (res.startsWith("jar:file:")) { + // This is a tag file packaged in a jar that is being checked + // for a dependency + result = new URL(res); + + } else { + result = context.getResource(canonicalURI(res)); + } + return result; + } + + + public Set getResourcePaths(String path) { + return context.getResourcePaths(canonicalURI(path)); + } + + /** + * Gets the actual path of a URI relative to the context of + * the compilation. + */ + public String getRealPath(String path) { + if (context != null) { + return context.getRealPath(path); + } + return path; + } + + /** + * Returns the tag-file-name-to-JAR-file map of this compilation unit, + * which maps tag file names to the JAR files in which the tag files are + * packaged. + * + * The map is populated when parsing the tag-file elements of the TLDs + * of any imported taglibs. + */ + public URL getTagFileJarUrl(String tagFile) { + return this.tagFileJarUrls.get(tagFile); + } + + public void setTagFileJarUrl(String tagFile, URL tagFileURL) { + this.tagFileJarUrls.put(tagFile, tagFileURL); + } + + /** + * Returns the JAR file in which the tag file for which this + * JspCompilationContext was created is packaged, or null if this + * JspCompilationContext does not correspond to a tag file, or if the + * corresponding tag file is not packaged in a JAR. + */ + public URL getTagFileJarUrl() { + return this.tagFileJarUrl; + } + + /* ==================== Common implementation ==================== */ + + /** + * Just the class name (does not include package name) of the + * generated class. + */ + public String getServletClassName() { + + if (className != null) { + return className; + } + + if (isTagFile) { + className = tagInfo.getTagClassName(); + int lastIndex = className.lastIndexOf('.'); + if (lastIndex != -1) { + className = className.substring(lastIndex + 1); + } + } else { + int iSep = jspUri.lastIndexOf('/') + 1; + className = JspUtil.makeJavaIdentifier(jspUri.substring(iSep)); + } + return className; + } + + public void setServletClassName(String className) { + this.className = className; + } + + /** + * Path of the JSP URI. Note that this is not a file name. This is + * the context rooted URI of the JSP file. + */ + public String getJspFile() { + return jspUri; + } + + /** + * Are we processing something that has been declared as an + * errorpage? + */ + public boolean isErrorPage() { + return isErrPage; + } + + public void setErrorPage(boolean isErrPage) { + this.isErrPage = isErrPage; + } + + public boolean isTagFile() { + return isTagFile; + } + + public TagInfo getTagInfo() { + return tagInfo; + } + + public void setTagInfo(TagInfo tagi) { + tagInfo = tagi; + } + + /** + * True if we are compiling a tag file in prototype mode. + * ie we only generate codes with class for the tag handler with empty + * method bodies. + */ + public boolean isPrototypeMode() { + return protoTypeMode; + } + + public void setPrototypeMode(boolean pm) { + protoTypeMode = pm; + } + + /** + * Package name for the generated class is make up of the base package + * name, which is user settable, and the derived package name. The + * derived package name directly mirrors the file heirachy of the JSP page. + */ + public String getServletPackageName() { + if (isTagFile()) { + String className = tagInfo.getTagClassName(); + int lastIndex = className.lastIndexOf('.'); + String pkgName = ""; + if (lastIndex != -1) { + pkgName = className.substring(0, lastIndex); + } + return pkgName; + } else { + String dPackageName = getDerivedPackageName(); + if (dPackageName.length() == 0) { + return basePackageName; + } + return basePackageName + '.' + getDerivedPackageName(); + } + } + + protected String getDerivedPackageName() { + if (derivedPackageName == null) { + int iSep = jspUri.lastIndexOf('/'); + derivedPackageName = (iSep > 0) ? + JspUtil.makeJavaPackage(jspUri.substring(1,iSep)) : ""; + } + return derivedPackageName; + } + + /** + * The package name into which the servlet class is generated. + */ + public void setServletPackageName(String servletPackageName) { + this.basePackageName = servletPackageName; + } + + /** + * Full path name of the Java file into which the servlet is being + * generated. + */ + public String getServletJavaFileName() { + if (servletJavaFileName == null) { + servletJavaFileName = getOutputDir() + getServletClassName() + ".java"; + } + return servletJavaFileName; + } + + /** + * Get hold of the Options object for this context. + */ + public Options getOptions() { + return options; + } + + public ServletContext getServletContext() { + return context; + } + + public JspRuntimeContext getRuntimeContext() { + return rctxt; + } + + /** + * Path of the Java file relative to the work directory. + */ + public String getJavaPath() { + + if (javaPath != null) { + return javaPath; + } + + if (isTagFile()) { + String tagName = tagInfo.getTagClassName(); + javaPath = tagName.replace('.', '/') + ".java"; + } else { + javaPath = getServletPackageName().replace('.', '/') + '/' + + getServletClassName() + ".java"; + } + return javaPath; + } + + public String getClassFileName() { + if (classFileName == null) { + classFileName = getOutputDir() + getServletClassName() + ".class"; + } + return classFileName; + } + + /** + * Get the content type of this JSP. + * + * Content type includes content type and encoding. + */ + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + /** + * Where is the servlet being generated? + */ + public ServletWriter getWriter() { + return writer; + } + + public void setWriter(ServletWriter writer) { + this.writer = writer; + } + + /** + * Gets the 'location' of the TLD associated with the given taglib 'uri'. + * + * @return An array of two Strings: The first element denotes the real + * path to the TLD. If the path to the TLD points to a jar file, then the + * second element denotes the name of the TLD entry in the jar file. + * Returns null if the given uri is not associated with any tag library + * 'exposed' in the web application. + */ + public String[] getTldLocation(String uri) throws JasperException { + String[] location = + getOptions().getTldLocationsCache().getLocation(uri); + return location; + } + + /** + * Are we keeping generated code around? + */ + public boolean keepGenerated() { + return getOptions().getKeepGenerated(); + } + + // ==================== Removal ==================== + + public void incrementRemoved() { + if (removed == 0 && rctxt != null) { + rctxt.removeWrapper(jspUri); + } + removed++; + } + + public boolean isRemoved() { + if (removed > 1 ) { + return true; + } + return false; + } + + // ==================== Compile and reload ==================== + + public void compile() throws JasperException, FileNotFoundException { + createCompiler(); + if (jspCompiler.isOutDated()) { + try { + jspCompiler.removeGeneratedFiles(); + jspLoader = null; + jspCompiler.compile(); + jsw.setReload(true); + jsw.setCompilationException(null); + } catch (JasperException ex) { + // Cache compilation exception + jsw.setCompilationException(ex); + throw ex; + } catch (Exception ex) { + JasperException je = new JasperException( + Localizer.getMessage("jsp.error.unable.compile"), + ex); + // Cache compilation exception + jsw.setCompilationException(je); + throw je; + } + } + } + + // ==================== Manipulating the class ==================== + + public Class load() + throws JasperException, FileNotFoundException + { + try { + getJspLoader(); + + String name; + if (isTagFile()) { + name = tagInfo.getTagClassName(); + } else { + name = getServletPackageName() + "." + getServletClassName(); + } + servletClass = jspLoader.loadClass(name); + } catch (ClassNotFoundException cex) { + throw new JasperException(Localizer.getMessage("jsp.error.unable.load"), + cex); + } catch (Exception ex) { + throw new JasperException(Localizer.getMessage("jsp.error.unable.compile"), + ex); + } + removed = 0; + return servletClass; + } + + // ==================== protected methods ==================== + + static Object outputDirLock = new Object(); + + public void checkOutputDir() { + if (outputDir != null) { + if (!(new File(outputDir)).exists()) { + makeOutputDir(); + } + } else { + createOutputDir(); + } + } + + protected boolean makeOutputDir() { + synchronized(outputDirLock) { + File outDirFile = new File(outputDir); + return (outDirFile.exists() || outDirFile.mkdirs()); + } + } + + protected void createOutputDir() { + String path = null; + if (isTagFile()) { + String tagName = tagInfo.getTagClassName(); + path = tagName.replace('.', File.separatorChar); + path = path.substring(0, path.lastIndexOf(File.separatorChar)); + } else { + path = getServletPackageName().replace('.',File.separatorChar); + } + + // Append servlet or tag handler path to scratch dir + try { + File base = options.getScratchDir(); + baseUrl = base.toURI().toURL(); + outputDir = base.getAbsolutePath() + File.separator + path + + File.separator; + if (!makeOutputDir()) { + throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder")); + } + } catch (MalformedURLException e) { + throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"), e); + } + } + + protected static final boolean isPathSeparator(char c) { + return (c == '/' || c == '\\'); + } + + protected static final String canonicalURI(String s) { + if (s == null) return null; + StringBuffer result = new StringBuffer(); + final int len = s.length(); + int pos = 0; + while (pos < len) { + char c = s.charAt(pos); + if ( isPathSeparator(c) ) { + /* + * multiple path separators. + * 'foo///bar' -> 'foo/bar' + */ + while (pos+1 < len && isPathSeparator(s.charAt(pos+1))) { + ++pos; + } + + if (pos+1 < len && s.charAt(pos+1) == '.') { + /* + * a single dot at the end of the path - we are done. + */ + if (pos+2 >= len) break; + + switch (s.charAt(pos+2)) { + /* + * self directory in path + * foo/./bar -> foo/bar + */ + case '/': + case '\\': + pos += 2; + continue; + + /* + * two dots in a path: go back one hierarchy. + * foo/bar/../baz -> foo/baz + */ + case '.': + // only if we have exactly _two_ dots. + if (pos+3 < len && isPathSeparator(s.charAt(pos+3))) { + pos += 3; + int separatorPos = result.length()-1; + while (separatorPos >= 0 && + ! isPathSeparator(result + .charAt(separatorPos))) { + --separatorPos; + } + if (separatorPos >= 0) + result.setLength(separatorPos); + continue; + } + } + } + } + result.append(c); + ++pos; + } + return result.toString(); + } +} + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/Options.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/Options.java new file mode 100644 index 000000000..f52b370c1 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/Options.java @@ -0,0 +1,202 @@ +/* + * 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; + +import java.io.File; +import java.util.Map; + +import org.apache.jasper.compiler.JspConfig; +import org.apache.jasper.compiler.TagPluginManager; +import org.apache.jasper.compiler.TldLocationsCache; + +/** + * A class to hold all init parameters specific to the JSP engine. + * + * @author Anil K. Vijendran + * @author Hans Bergsten + * @author Pierre Delisle + */ +public interface Options { + + /** + * Returns true if Jasper issues a compilation error instead of a runtime + * Instantiation error if the class attribute specified in useBean action + * is invalid. + */ + public boolean getErrorOnUseBeanInvalidClassAttribute(); + + /** + * Are we keeping generated code around? + */ + public boolean getKeepGenerated(); + + /** + * Returns true if tag handler pooling is enabled, false otherwise. + */ + public boolean isPoolingEnabled(); + + /** + * Are we supporting HTML mapped servlets? + */ + public boolean getMappedFile(); + + /** + * Should errors be sent to client or thrown into stderr? + * @deprecated + */ + @Deprecated + public boolean getSendErrorToClient(); + + /** + * Should we include debug information in compiled class? + */ + public boolean getClassDebugInfo(); + + /** + * Background compile thread check interval in seconds + */ + public int getCheckInterval(); + + /** + * Is Jasper being used in development mode? + */ + public boolean getDevelopment(); + + /** + * Should we include a source fragment in exception messages, which could be displayed + * to the developer ? + */ + public boolean getDisplaySourceFragment(); + + /** + * Is the generation of SMAP info for JSR45 debugging suppressed? + */ + public boolean isSmapSuppressed(); + + /** + * Indicates whether SMAP info for JSR45 debugging should be dumped to a + * file. + * Ignored is suppressSmap() is true + */ + public boolean isSmapDumped(); + + /** + * Should white spaces between directives or actions be trimmed? + */ + public boolean getTrimSpaces(); + + /** + * Class ID for use in the plugin tag when the browser is IE. + */ + public String getIeClassId(); + + /** + * What is my scratch dir? + */ + public File getScratchDir(); + + /** + * What classpath should I use while compiling the servlets + * generated from JSP files? + */ + public String getClassPath(); + + /** + * Compiler to use. + */ + public String getCompiler(); + + /** + * The compiler target VM, e.g. 1.1, 1.2, 1.3, 1.4, or 1.5. + */ + public String getCompilerTargetVM(); + + /** + * Compiler source VM, e.g. 1.3, 1.4, or 1.5. + */ + public String getCompilerSourceVM(); + + /** + * Java compiler class to use. + */ + public String getCompilerClassName(); + + /** + * The cache for the location of the TLD's + * for the various tag libraries 'exposed' + * by the web application. + * A tag library is 'exposed' either explicitely in + * web.xml or implicitely via the uri tag in the TLD + * of a taglib deployed in a jar file (WEB-INF/lib). + * + * @return the instance of the TldLocationsCache + * for the web-application. + */ + public TldLocationsCache getTldLocationsCache(); + + /** + * Java platform encoding to generate the JSP + * page servlet. + */ + public String getJavaEncoding(); + + /** + * boolean flag to tell Ant whether to fork JSP page compilations. + */ + public boolean getFork(); + + /** + * Obtain JSP configuration informantion specified in web.xml. + */ + public JspConfig getJspConfig(); + + /** + * Is generation of X-Powered-By response header enabled/disabled? + */ + public boolean isXpoweredBy(); + + /** + * Obtain a Tag Plugin Manager + */ + public TagPluginManager getTagPluginManager(); + + /** + * Are Text strings to be generated as char arrays? + */ + public boolean genStringAsCharArray(); + + /** + * Modification test interval. + */ + public int getModificationTestInterval(); + + /** + * Is caching enabled (used for precompilation). + */ + public boolean isCaching(); + + /** + * The web-application wide cache for the returned TreeNode + * by parseXMLDocument in TagLibraryInfoImpl.parseTLD, + * if isCaching returns true. + * + * @return the Map(String uri, TreeNode tld) instance. + */ + public Map getCache(); + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/AntCompiler.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/AntCompiler.java new file mode 100644 index 000000000..351267a5b --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/AntCompiler.java @@ -0,0 +1,493 @@ +/* + * 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.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import java.util.StringTokenizer; + +import org.apache.jasper.JasperException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DefaultLogger; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Javac; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.PatternSet; + +/** + * Main JSP compiler class. This class uses Ant for compiling. + * + * @author Anil K. Vijendran + * @author Mandar Raje + * @author Pierre Delisle + * @author Kin-man Chung + * @author Remy Maucherat + * @author Mark Roth + */ +public class AntCompiler extends Compiler { + + protected static Object javacLock = new Object(); + + static { + System.setErr(new SystemLogHandler(System.err)); + } + + // ----------------------------------------------------- Instance Variables + + protected Project project = null; + protected JasperAntLogger logger; + + // ------------------------------------------------------------ Constructor + + // Lazy eval - if we don't need to compile we probably don't need the project + protected Project getProject() { + + if (project != null) + return project; + + // Initializing project + project = new Project(); + logger = new JasperAntLogger(); + logger.setOutputPrintStream(System.out); + logger.setErrorPrintStream(System.err); + logger.setMessageOutputLevel(Project.MSG_INFO); + project.addBuildListener( logger); + if (System.getProperty("catalina.home") != null) { + project.setBasedir( System.getProperty("catalina.home")); + } + + if( options.getCompiler() != null ) { + if( log.isDebugEnabled() ) + log.debug("Compiler " + options.getCompiler() ); + project.setProperty("build.compiler", options.getCompiler() ); + } + project.init(); + return project; + } + + public class JasperAntLogger extends DefaultLogger { + + protected StringBuffer reportBuf = new StringBuffer(); + + protected void printMessage(final String message, + final PrintStream stream, + final int priority) { + } + + protected void log(String message) { + reportBuf.append(message); + reportBuf.append(System.getProperty("line.separator")); + } + + protected String getReport() { + String report = reportBuf.toString(); + reportBuf.setLength(0); + return report; + } + } + + // --------------------------------------------------------- Public Methods + + + /** + * Compile the servlet from .java file to .class file + */ + protected void generateClass(String[] smap) + throws FileNotFoundException, JasperException, Exception { + + long t1 = 0; + if (log.isDebugEnabled()) { + t1 = System.currentTimeMillis(); + } + + String javaEncoding = ctxt.getOptions().getJavaEncoding(); + String javaFileName = ctxt.getServletJavaFileName(); + String classpath = ctxt.getClassPath(); + + String sep = System.getProperty("path.separator"); + + StringBuffer errorReport = new StringBuffer(); + + StringBuffer info=new StringBuffer(); + info.append("Compile: javaFileName=" + javaFileName + "\n" ); + info.append(" classpath=" + classpath + "\n" ); + + // Start capturing the System.err output for this thread + SystemLogHandler.setThread(); + + // Initializing javac task + getProject(); + Javac javac = (Javac) project.createTask("javac"); + + // Initializing classpath + Path path = new Path(project); + path.setPath(System.getProperty("java.class.path")); + info.append(" cp=" + System.getProperty("java.class.path") + "\n"); + StringTokenizer tokenizer = new StringTokenizer(classpath, sep); + while (tokenizer.hasMoreElements()) { + String pathElement = tokenizer.nextToken(); + File repository = new File(pathElement); + path.setLocation(repository); + info.append(" cp=" + repository + "\n"); + } + + if( log.isDebugEnabled() ) + log.debug( "Using classpath: " + System.getProperty("java.class.path") + sep + + classpath); + + // Initializing sourcepath + Path srcPath = new Path(project); + srcPath.setLocation(options.getScratchDir()); + + info.append(" work dir=" + options.getScratchDir() + "\n"); + + // Initialize and set java extensions + String exts = System.getProperty("java.ext.dirs"); + if (exts != null) { + Path extdirs = new Path(project); + extdirs.setPath(exts); + javac.setExtdirs(extdirs); + info.append(" extension dir=" + exts + "\n"); + } + + // Add endorsed directories if any are specified and we're forking + // See Bugzilla 31257 + if(ctxt.getOptions().getFork()) { + String endorsed = System.getProperty("java.endorsed.dirs"); + if(endorsed != null) { + Javac.ImplementationSpecificArgument endorsedArg = + javac.createCompilerArg(); + endorsedArg.setLine("-J-Djava.endorsed.dirs=" + + quotePathList(endorsed)); + info.append(" endorsed dir=" + quotePathList(endorsed) + + "\n"); + } else { + info.append(" no endorsed dirs specified\n"); + } + } + + // Configure the compiler object + javac.setEncoding(javaEncoding); + javac.setClasspath(path); + javac.setDebug(ctxt.getOptions().getClassDebugInfo()); + javac.setSrcdir(srcPath); + javac.setTempdir(options.getScratchDir()); + javac.setOptimize(! ctxt.getOptions().getClassDebugInfo() ); + javac.setFork(ctxt.getOptions().getFork()); + info.append(" srcDir=" + srcPath + "\n" ); + + // Set the Java compiler to use + if (options.getCompiler() != null) { + javac.setCompiler(options.getCompiler()); + info.append(" compiler=" + options.getCompiler() + "\n"); + } + + if (options.getCompilerTargetVM() != null) { + javac.setTarget(options.getCompilerTargetVM()); + info.append(" compilerTargetVM=" + options.getCompilerTargetVM() + "\n"); + } + + if (options.getCompilerSourceVM() != null) { + javac.setSource(options.getCompilerSourceVM()); + info.append(" compilerSourceVM=" + options.getCompilerSourceVM() + "\n"); + } + + // Build includes path + PatternSet.NameEntry includes = javac.createInclude(); + + includes.setName(ctxt.getJavaPath()); + info.append(" include="+ ctxt.getJavaPath() + "\n" ); + + BuildException be = null; + + try { + if (ctxt.getOptions().getFork()) { + javac.execute(); + } else { + synchronized(javacLock) { + javac.execute(); + } + } + } catch (BuildException e) { + be = e; + log.error(Localizer.getMessage("jsp.error.javac"), e); + log.error(Localizer.getMessage("jsp.error.javac.env") + info.toString()); + } + + errorReport.append(logger.getReport()); + + // Stop capturing the System.err output for this thread + String errorCapture = SystemLogHandler.unsetThread(); + if (errorCapture != null) { + errorReport.append(System.getProperty("line.separator")); + errorReport.append(errorCapture); + } + + if (!ctxt.keepGenerated()) { + File javaFile = new File(javaFileName); + javaFile.delete(); + } + + if (be != null) { + String errorReportString = errorReport.toString(); + log.error(Localizer.getMessage("jsp.error.compilation", javaFileName, errorReportString)); + JavacErrorDetail[] javacErrors = ErrorDispatcher.parseJavacErrors( + errorReportString, javaFileName, pageNodes); + if (javacErrors != null) { + errDispatcher.javacError(javacErrors); + } else { + errDispatcher.javacError(errorReportString, be); + } + } + + if( log.isDebugEnabled() ) { + long t2 = System.currentTimeMillis(); + log.debug("Compiled " + ctxt.getServletJavaFileName() + " " + + (t2-t1) + "ms"); + } + + logger = null; + project = null; + + if (ctxt.isPrototypeMode()) { + return; + } + + // JSR45 Support + if (! options.isSmapSuppressed()) { + SmapUtil.installSmap(smap); + } + } + + private String quotePathList(String list) { + StringBuffer result = new StringBuffer(list.length() + 10); + StringTokenizer st = new StringTokenizer(list, File.pathSeparator); + while (st.hasMoreTokens()) { + String token = st.nextToken(); + if (token.indexOf(' ') == -1) { + result.append(token); + } else { + result.append('\"'); + result.append(token); + result.append('\"'); + } + if (st.hasMoreTokens()) { + result.append(File.pathSeparatorChar); + } + } + return result.toString(); + } + + + protected static class SystemLogHandler extends PrintStream { + + + // ----------------------------------------------------------- Constructors + + + /** + * Construct the handler to capture the output of the given steam. + */ + public SystemLogHandler(PrintStream wrapped) { + super(wrapped); + this.wrapped = wrapped; + } + + + // ----------------------------------------------------- Instance Variables + + + /** + * Wrapped PrintStream. + */ + protected PrintStream wrapped = null; + + + /** + * Thread <-> PrintStream associations. + */ + protected static ThreadLocal streams = new ThreadLocal(); + + + /** + * Thread <-> ByteArrayOutputStream associations. + */ + protected static ThreadLocal data = new ThreadLocal(); + + + // --------------------------------------------------------- Public Methods + + + public PrintStream getWrapped() { + return wrapped; + } + + /** + * Start capturing thread's output. + */ + public static void setThread() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + data.set(baos); + streams.set(new PrintStream(baos)); + } + + + /** + * Stop capturing thread's output and return captured data as a String. + */ + public static String unsetThread() { + ByteArrayOutputStream baos = + (ByteArrayOutputStream) data.get(); + if (baos == null) { + return null; + } + streams.set(null); + data.set(null); + return baos.toString(); + } + + + // ------------------------------------------------------ Protected Methods + + + /** + * Find PrintStream to which the output must be written to. + */ + protected PrintStream findStream() { + PrintStream ps = (PrintStream) streams.get(); + if (ps == null) { + ps = wrapped; + } + return ps; + } + + + // ---------------------------------------------------- PrintStream Methods + + + public void flush() { + findStream().flush(); + } + + public void close() { + findStream().close(); + } + + public boolean checkError() { + return findStream().checkError(); + } + + protected void setError() { + //findStream().setError(); + } + + public void write(int b) { + findStream().write(b); + } + + public void write(byte[] b) + throws IOException { + findStream().write(b); + } + + public void write(byte[] buf, int off, int len) { + findStream().write(buf, off, len); + } + + public void print(boolean b) { + findStream().print(b); + } + + public void print(char c) { + findStream().print(c); + } + + public void print(int i) { + findStream().print(i); + } + + public void print(long l) { + findStream().print(l); + } + + public void print(float f) { + findStream().print(f); + } + + public void print(double d) { + findStream().print(d); + } + + public void print(char[] s) { + findStream().print(s); + } + + public void print(String s) { + findStream().print(s); + } + + public void print(Object obj) { + findStream().print(obj); + } + + public void println() { + findStream().println(); + } + + public void println(boolean x) { + findStream().println(x); + } + + public void println(char x) { + findStream().println(x); + } + + public void println(int x) { + findStream().println(x); + } + + public void println(long x) { + findStream().println(x); + } + + public void println(float x) { + findStream().println(x); + } + + public void println(double x) { + findStream().println(x); + } + + public void println(char[] x) { + findStream().println(x); + } + + public void println(String x) { + findStream().println(x); + } + + public void println(Object x) { + findStream().println(x); + } + + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/BeanRepository.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/BeanRepository.java new file mode 100644 index 000000000..cd32b7fa4 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/BeanRepository.java @@ -0,0 +1,75 @@ +/* + * 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.util.HashMap; + +import org.apache.jasper.JasperException; + +/** + * Repository of {page, request, session, application}-scoped beans + * + * @author Mandar Raje + * @author Remy Maucherat + */ +public class BeanRepository { + + protected HashMap beanTypes; + protected ClassLoader loader; + protected ErrorDispatcher errDispatcher; + + /** + * Constructor. + */ + public BeanRepository(ClassLoader loader, ErrorDispatcher err) { + this.loader = loader; + this.errDispatcher = err; + beanTypes = new HashMap(); + } + + public void addBean(Node.UseBean n, String s, String type, String scope) + throws JasperException { + + if (!(scope == null || scope.equals("page") || scope.equals("request") + || scope.equals("session") || scope.equals("application"))) { + errDispatcher.jspError(n, "jsp.error.usebean.badScope"); + } + + beanTypes.put(s, type); + } + + public Class getBeanType(String bean) + throws JasperException { + Class clazz = null; + try { + clazz = loader.loadClass(beanTypes.get(bean)); + } catch (ClassNotFoundException ex) { + throw new JasperException (ex); + } + return clazz; + } + + public boolean checkVariable(String bean) { + return beanTypes.containsKey(bean); + } + +} + + + + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Collector.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Collector.java new file mode 100644 index 000000000..50b2b9de4 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Collector.java @@ -0,0 +1,204 @@ +/* + * 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 org.apache.jasper.JasperException; + +/** + * Collect info about the page and nodes, and make them availabe through + * the PageInfo object. + * + * @author Kin-man Chung + * @author Mark Roth + */ + +class Collector { + + /** + * A visitor for collecting information on the page and the body of + * the custom tags. + */ + static class CollectVisitor extends Node.Visitor { + + private boolean scriptingElementSeen = false; + private boolean usebeanSeen = false; + private boolean includeActionSeen = false; + private boolean paramActionSeen = false; + private boolean setPropertySeen = false; + private boolean hasScriptingVars = false; + + public void visit(Node.ParamAction n) throws JasperException { + if (n.getValue().isExpression()) { + scriptingElementSeen = true; + } + paramActionSeen = true; + } + + public void visit(Node.IncludeAction n) throws JasperException { + if (n.getPage().isExpression()) { + scriptingElementSeen = true; + } + includeActionSeen = true; + visitBody(n); + } + + public void visit(Node.ForwardAction n) throws JasperException { + if (n.getPage().isExpression()) { + scriptingElementSeen = true; + } + visitBody(n); + } + + public void visit(Node.SetProperty n) throws JasperException { + if (n.getValue() != null && n.getValue().isExpression()) { + scriptingElementSeen = true; + } + setPropertySeen = true; + } + + public void visit(Node.UseBean n) throws JasperException { + if (n.getBeanName() != null && n.getBeanName().isExpression()) { + scriptingElementSeen = true; + } + usebeanSeen = true; + visitBody(n); + } + + public void visit(Node.PlugIn n) throws JasperException { + if (n.getHeight() != null && n.getHeight().isExpression()) { + scriptingElementSeen = true; + } + if (n.getWidth() != null && n.getWidth().isExpression()) { + scriptingElementSeen = true; + } + visitBody(n); + } + + public void visit(Node.CustomTag n) throws JasperException { + // Check to see what kinds of element we see as child elements + checkSeen( n.getChildInfo(), n ); + } + + /** + * Check all child nodes for various elements and update the given + * ChildInfo object accordingly. Visits body in the process. + */ + private void checkSeen( Node.ChildInfo ci, Node n ) + throws JasperException + { + // save values collected so far + boolean scriptingElementSeenSave = scriptingElementSeen; + scriptingElementSeen = false; + boolean usebeanSeenSave = usebeanSeen; + usebeanSeen = false; + boolean includeActionSeenSave = includeActionSeen; + includeActionSeen = false; + boolean paramActionSeenSave = paramActionSeen; + paramActionSeen = false; + boolean setPropertySeenSave = setPropertySeen; + setPropertySeen = false; + boolean hasScriptingVarsSave = hasScriptingVars; + hasScriptingVars = false; + + // Scan attribute list for expressions + if( n instanceof Node.CustomTag ) { + Node.CustomTag ct = (Node.CustomTag)n; + Node.JspAttribute[] attrs = ct.getJspAttributes(); + for (int i = 0; attrs != null && i < attrs.length; i++) { + if (attrs[i].isExpression()) { + scriptingElementSeen = true; + break; + } + } + } + + visitBody(n); + + if( (n instanceof Node.CustomTag) && !hasScriptingVars) { + Node.CustomTag ct = (Node.CustomTag)n; + hasScriptingVars = ct.getVariableInfos().length > 0 || + ct.getTagVariableInfos().length > 0; + } + + // Record if the tag element and its body contains any scriptlet. + ci.setScriptless(! scriptingElementSeen); + ci.setHasUseBean(usebeanSeen); + ci.setHasIncludeAction(includeActionSeen); + ci.setHasParamAction(paramActionSeen); + ci.setHasSetProperty(setPropertySeen); + ci.setHasScriptingVars(hasScriptingVars); + + // Propagate value of scriptingElementSeen up. + scriptingElementSeen = scriptingElementSeen || scriptingElementSeenSave; + usebeanSeen = usebeanSeen || usebeanSeenSave; + setPropertySeen = setPropertySeen || setPropertySeenSave; + includeActionSeen = includeActionSeen || includeActionSeenSave; + paramActionSeen = paramActionSeen || paramActionSeenSave; + hasScriptingVars = hasScriptingVars || hasScriptingVarsSave; + } + + public void visit(Node.JspElement n) throws JasperException { + if (n.getNameAttribute().isExpression()) + scriptingElementSeen = true; + + Node.JspAttribute[] attrs = n.getJspAttributes(); + for (int i = 0; i < attrs.length; i++) { + if (attrs[i].isExpression()) { + scriptingElementSeen = true; + break; + } + } + visitBody(n); + } + + public void visit(Node.JspBody n) throws JasperException { + checkSeen( n.getChildInfo(), n ); + } + + public void visit(Node.NamedAttribute n) throws JasperException { + checkSeen( n.getChildInfo(), n ); + } + + public void visit(Node.Declaration n) throws JasperException { + scriptingElementSeen = true; + } + + public void visit(Node.Expression n) throws JasperException { + scriptingElementSeen = true; + } + + public void visit(Node.Scriptlet n) throws JasperException { + scriptingElementSeen = true; + } + + public void updatePageInfo(PageInfo pageInfo) { + pageInfo.setScriptless(! scriptingElementSeen); + } + } + + + public static void collect(Compiler compiler, Node.Nodes page) + throws JasperException { + + CollectVisitor collectVisitor = new CollectVisitor(); + page.visit(collectVisitor); + collectVisitor.updatePageInfo(compiler.getPageInfo()); + + } +} + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java new file mode 100644 index 000000000..9a764f1d8 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java @@ -0,0 +1,551 @@ +/* + * 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.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.Iterator; +import java.util.List; + +import org.apache.jasper.JasperException; +import org.apache.jasper.JspCompilationContext; +import org.apache.jasper.Options; +import org.apache.jasper.servlet.JspServletWrapper; + +/** + * Main JSP compiler class. This class uses Ant for compiling. + * + * @author Anil K. Vijendran + * @author Mandar Raje + * @author Pierre Delisle + * @author Kin-man Chung + * @author Remy Maucherat + * @author Mark Roth + */ +public abstract class Compiler { + + protected org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory + .getLog(Compiler.class); + + // ----------------------------------------------------- Instance Variables + + protected JspCompilationContext ctxt; + + protected ErrorDispatcher errDispatcher; + + protected PageInfo pageInfo; + + protected JspServletWrapper jsw; + + protected TagFileProcessor tfp; + + protected Options options; + + protected Node.Nodes pageNodes; + + // ------------------------------------------------------------ Constructor + + public void init(JspCompilationContext ctxt, JspServletWrapper jsw) { + this.jsw = jsw; + this.ctxt = ctxt; + this.options = ctxt.getOptions(); + } + + // --------------------------------------------------------- Public Methods + + /** + *

+ * Retrieves the parsed nodes of the JSP page, if they are available. May + * return null. Used in development mode for generating detailed error + * messages. http://issues.apache.org/bugzilla/show_bug.cgi?id=37062. + *

+ */ + public Node.Nodes getPageNodes() { + return this.pageNodes; + } + + /** + * Compile the jsp file into equivalent servlet in .java file + * + * @return a smap for the current JSP page, if one is generated, null + * otherwise + */ + protected String[] generateJava() throws Exception { + + String[] smapStr = null; + + long t1, t2, t3, t4; + + t1 = t2 = t3 = t4 = 0; + + if (log.isDebugEnabled()) { + t1 = System.currentTimeMillis(); + } + + // Setup page info area + pageInfo = new PageInfo(new BeanRepository(ctxt.getClassLoader(), + errDispatcher), ctxt.getJspFile()); + + JspConfig jspConfig = options.getJspConfig(); + JspConfig.JspProperty jspProperty = jspConfig.findJspProperty(ctxt + .getJspFile()); + + /* + * If the current uri is matched by a pattern specified in a + * jsp-property-group in web.xml, initialize pageInfo with those + * properties. + */ + if (jspProperty.isELIgnored() != null) { + pageInfo.setELIgnored(JspUtil.booleanValue(jspProperty + .isELIgnored())); + } + if (jspProperty.isScriptingInvalid() != null) { + pageInfo.setScriptingInvalid(JspUtil.booleanValue(jspProperty + .isScriptingInvalid())); + } + if (jspProperty.getIncludePrelude() != null) { + pageInfo.setIncludePrelude(jspProperty.getIncludePrelude()); + } + if (jspProperty.getIncludeCoda() != null) { + pageInfo.setIncludeCoda(jspProperty.getIncludeCoda()); + } + if (jspProperty.isDeferedSyntaxAllowedAsLiteral() != null) { + pageInfo.setDeferredSyntaxAllowedAsLiteral(JspUtil.booleanValue(jspProperty + .isDeferedSyntaxAllowedAsLiteral())); + } + if (jspProperty.isTrimDirectiveWhitespaces() != null) { + pageInfo.setTrimDirectiveWhitespaces(JspUtil.booleanValue(jspProperty + .isTrimDirectiveWhitespaces())); + } + + ctxt.checkOutputDir(); + String javaFileName = ctxt.getServletJavaFileName(); + + ServletWriter writer = null; + try { + /* + * The setting of isELIgnored changes the behaviour of the parser + * in subtle ways. To add to the 'fun', isELIgnored can be set in + * any file that forms part of the translation unit so setting it + * in a file included towards the end of the translation unit can + * change how the parser should have behaved when parsing content + * up to the point where isELIgnored was set. Arghh! + * Previous attempts to hack around this have only provided partial + * solutions. We now use two passes to parse the translation unit. + * The first just parses the directives and the second parses the + * whole translation unit once we know how isELIgnored has been set. + * TODO There are some possible optimisations of this process. + */ + // Parse the file + ParserController parserCtl = new ParserController(ctxt, this); + + // Pass 1 - the directives + Node.Nodes directives = + parserCtl.parseDirectives(ctxt.getJspFile()); + Validator.validateDirectives(this, directives); + + // Pass 2 - the whole translation unit + pageNodes = parserCtl.parse(ctxt.getJspFile()); + + if (ctxt.isPrototypeMode()) { + // generate prototype .java file for the tag file + writer = setupContextWriter(javaFileName); + Generator.generate(writer, this, pageNodes); + writer.close(); + writer = null; + return null; + } + + // Validate and process attributes - don't re-validate the + // directives we validated in pass 1 + Validator.validateExDirectives(this, pageNodes); + + if (log.isDebugEnabled()) { + t2 = System.currentTimeMillis(); + } + + // Collect page info + Collector.collect(this, pageNodes); + + // Compile (if necessary) and load the tag files referenced in + // this compilation unit. + tfp = new TagFileProcessor(); + tfp.loadTagFiles(this, pageNodes); + + if (log.isDebugEnabled()) { + t3 = System.currentTimeMillis(); + } + + // Determine which custom tag needs to declare which scripting vars + ScriptingVariabler.set(pageNodes, errDispatcher); + + // Optimizations by Tag Plugins + TagPluginManager tagPluginManager = options.getTagPluginManager(); + tagPluginManager.apply(pageNodes, errDispatcher, pageInfo); + + // Optimization: concatenate contiguous template texts. + TextOptimizer.concatenate(this, pageNodes); + + // Generate static function mapper codes. + ELFunctionMapper.map(this, pageNodes); + + // generate servlet .java file + writer = setupContextWriter(javaFileName); + Generator.generate(writer, this, pageNodes); + writer.close(); + writer = null; + + // The writer is only used during the compile, dereference + // it in the JspCompilationContext when done to allow it + // to be GC'd and save memory. + ctxt.setWriter(null); + + if (log.isDebugEnabled()) { + t4 = System.currentTimeMillis(); + log.debug("Generated " + javaFileName + " total=" + (t4 - t1) + + " generate=" + (t4 - t3) + " validate=" + (t2 - t1)); + } + + } catch (Exception e) { + if (writer != null) { + try { + writer.close(); + writer = null; + } catch (Exception e1) { + // do nothing + } + } + // Remove the generated .java file + new File(javaFileName).delete(); + throw e; + } finally { + if (writer != null) { + try { + writer.close(); + } catch (Exception e2) { + // do nothing + } + } + } + + // JSR45 Support + if (!options.isSmapSuppressed()) { + smapStr = SmapUtil.generateSmap(ctxt, pageNodes); + } + + // If any proto type .java and .class files was generated, + // the prototype .java may have been replaced by the current + // compilation (if the tag file is self referencing), but the + // .class file need to be removed, to make sure that javac would + // generate .class again from the new .java file just generated. + tfp.removeProtoTypeFiles(ctxt.getClassFileName()); + + return smapStr; + } + + private ServletWriter setupContextWriter(String javaFileName) + throws FileNotFoundException, JasperException { + ServletWriter writer; + // Setup the ServletWriter + String javaEncoding = ctxt.getOptions().getJavaEncoding(); + OutputStreamWriter osw = null; + + try { + osw = new OutputStreamWriter( + new FileOutputStream(javaFileName), javaEncoding); + } catch (UnsupportedEncodingException ex) { + errDispatcher.jspError("jsp.error.needAlternateJavaEncoding", + javaEncoding); + } + + writer = new ServletWriter(new PrintWriter(osw)); + ctxt.setWriter(writer); + return writer; + } + + /** + * Compile the servlet from .java file to .class file + */ + protected abstract void generateClass(String[] smap) + throws FileNotFoundException, JasperException, Exception; + + /** + * Compile the jsp file from the current engine context + */ + public void compile() throws FileNotFoundException, JasperException, + Exception { + compile(true); + } + + /** + * Compile the jsp file from the current engine context. As an side- effect, + * tag files that are referenced by this page are also compiled. + * + * @param compileClass + * If true, generate both .java and .class file If false, + * generate only .java file + */ + public void compile(boolean compileClass) throws FileNotFoundException, + JasperException, Exception { + compile(compileClass, false); + } + + /** + * Compile the jsp file from the current engine context. As an side- effect, + * tag files that are referenced by this page are also compiled. + * + * @param compileClass + * If true, generate both .java and .class file If false, + * generate only .java file + * @param jspcMode + * true if invoked from JspC, false otherwise + */ + public void compile(boolean compileClass, boolean jspcMode) + throws FileNotFoundException, JasperException, Exception { + if (errDispatcher == null) { + this.errDispatcher = new ErrorDispatcher(jspcMode); + } + + try { + String[] smap = generateJava(); + if (compileClass) { + generateClass(smap); + // Fix for bugzilla 41606 + // Set JspServletWrapper.servletClassLastModifiedTime after successful compile + String targetFileName = ctxt.getClassFileName(); + if (targetFileName != null) { + File targetFile = new File(targetFileName); + if (targetFile.exists() && jsw != null) { + jsw.setServletClassLastModifiedTime(targetFile.lastModified()); + } + } + } + } finally { + if (tfp != null && ctxt.isPrototypeMode()) { + tfp.removeProtoTypeFiles(null); + } + // Make sure these object which are only used during the + // generation and compilation of the JSP page get + // dereferenced so that they can be GC'd and reduce the + // memory footprint. + tfp = null; + errDispatcher = null; + pageInfo = null; + + // Only get rid of the pageNodes if in production. + // In development mode, they are used for detailed + // error messages. + // http://issues.apache.org/bugzilla/show_bug.cgi?id=37062 + if (!this.options.getDevelopment()) { + pageNodes = null; + } + + if (ctxt.getWriter() != null) { + ctxt.getWriter().close(); + ctxt.setWriter(null); + } + } + } + + /** + * This is a protected method intended to be overridden by subclasses of + * Compiler. This is used by the compile method to do all the compilation. + */ + public boolean isOutDated() { + return isOutDated(true); + } + + /** + * Determine if a compilation is necessary by checking the time stamp of the + * JSP page with that of the corresponding .class or .java file. If the page + * has dependencies, the check is also extended to its dependeants, and so + * on. This method can by overidden by a subclasses of Compiler. + * + * @param checkClass + * If true, check against .class file, if false, check against + * .java file. + */ + public boolean isOutDated(boolean checkClass) { + + String jsp = ctxt.getJspFile(); + + if (jsw != null + && (ctxt.getOptions().getModificationTestInterval() > 0)) { + + if (jsw.getLastModificationTest() + + (ctxt.getOptions().getModificationTestInterval() * 1000) > System + .currentTimeMillis()) { + return false; + } else { + jsw.setLastModificationTest(System.currentTimeMillis()); + } + } + + long jspRealLastModified = 0; + try { + URL jspUrl = ctxt.getResource(jsp); + if (jspUrl == null) { + ctxt.incrementRemoved(); + return false; + } + URLConnection uc = jspUrl.openConnection(); + if (uc instanceof JarURLConnection) { + jspRealLastModified = + ((JarURLConnection) uc).getJarEntry().getTime(); + } else { + jspRealLastModified = uc.getLastModified(); + } + uc.getInputStream().close(); + } catch (Exception e) { + return true; + } + + long targetLastModified = 0; + File targetFile; + + if (checkClass) { + targetFile = new File(ctxt.getClassFileName()); + } else { + targetFile = new File(ctxt.getServletJavaFileName()); + } + + if (!targetFile.exists()) { + return true; + } + + targetLastModified = targetFile.lastModified(); + if (checkClass && jsw != null) { + jsw.setServletClassLastModifiedTime(targetLastModified); + } + if (targetLastModified < jspRealLastModified) { + if (log.isDebugEnabled()) { + log.debug("Compiler: outdated: " + targetFile + " " + + targetLastModified); + } + return true; + } + + // determine if source dependent files (e.g. includes using include + // directives) have been changed. + if (jsw == null) { + return false; + } + + List depends = jsw.getDependants(); + if (depends == null) { + return false; + } + + Iterator it = depends.iterator(); + while (it.hasNext()) { + String include = (String) it.next(); + try { + URL includeUrl = ctxt.getResource(include); + if (includeUrl == null) { + return true; + } + + URLConnection iuc = includeUrl.openConnection(); + long includeLastModified = 0; + if (iuc instanceof JarURLConnection) { + includeLastModified = + ((JarURLConnection) iuc).getJarEntry().getTime(); + } else { + includeLastModified = iuc.getLastModified(); + } + iuc.getInputStream().close(); + + if (includeLastModified > targetLastModified) { + return true; + } + } catch (Exception e) { + return true; + } + } + + return false; + + } + + /** + * Gets the error dispatcher. + */ + public ErrorDispatcher getErrorDispatcher() { + return errDispatcher; + } + + /** + * Gets the info about the page under compilation + */ + public PageInfo getPageInfo() { + return pageInfo; + } + + public JspCompilationContext getCompilationContext() { + return ctxt; + } + + /** + * Remove generated files + */ + public void removeGeneratedFiles() { + try { + String classFileName = ctxt.getClassFileName(); + if (classFileName != null) { + File classFile = new File(classFileName); + if (log.isDebugEnabled()) + log.debug("Deleting " + classFile); + classFile.delete(); + } + } catch (Exception e) { + // Remove as much as possible, ignore possible exceptions + } + try { + String javaFileName = ctxt.getServletJavaFileName(); + if (javaFileName != null) { + File javaFile = new File(javaFileName); + if (log.isDebugEnabled()) + log.debug("Deleting " + javaFile); + javaFile.delete(); + } + } catch (Exception e) { + // Remove as much as possible, ignore possible exceptions + } + } + + public void removeGeneratedClassFiles() { + try { + String classFileName = ctxt.getClassFileName(); + if (classFileName != null) { + File classFile = new File(classFileName); + if (log.isDebugEnabled()) + log.debug("Deleting " + classFile); + classFile.delete(); + } + } catch (Exception e) { + // Remove as much as possible, ignore possible exceptions + } + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java new file mode 100644 index 000000000..97d543c02 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java @@ -0,0 +1,109 @@ +/* + * 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 org.apache.jasper.JasperException; + +/** + * Default implementation of ErrorHandler interface. + * + * @author Jan Luehe + */ +class DefaultErrorHandler implements ErrorHandler { + + /* + * Processes the given JSP parse error. + * + * @param fname Name of the JSP file in which the parse error occurred + * @param line Parse error line number + * @param column Parse error column number + * @param errMsg Parse error message + * @param exception Parse exception + */ + public void jspError(String fname, int line, int column, String errMsg, + Exception ex) throws JasperException { + throw new JasperException(fname + "(" + line + "," + column + ")" + + " " + errMsg, ex); + } + + /* + * Processes the given JSP parse error. + * + * @param errMsg Parse error message + * @param exception Parse exception + */ + public void jspError(String errMsg, Exception ex) throws JasperException { + throw new JasperException(errMsg, ex); + } + + /* + * Processes the given javac compilation errors. + * + * @param details Array of JavacErrorDetail instances corresponding to the + * compilation errors + */ + public void javacError(JavacErrorDetail[] details) throws JasperException { + + if (details == null) { + return; + } + + Object[] args = null; + StringBuffer buf = new StringBuffer(); + + for (int i=0; i < details.length; i++) { + if (details[i].getJspBeginLineNumber() >= 0) { + args = new Object[] { + new Integer(details[i].getJspBeginLineNumber()), + details[i].getJspFileName() }; + buf.append("\n\n"); + buf.append(Localizer.getMessage("jsp.error.single.line.number", + args)); + buf.append("\n"); + buf.append(details[i].getErrorMessage()); + buf.append("\n"); + buf.append(details[i].getJspExtract()); + } else { + args = new Object[] { + new Integer(details[i].getJavaLineNumber()) }; + buf.append("\n\n"); + buf.append(Localizer.getMessage("jsp.error.java.line.number", + args)); + buf.append("\n"); + buf.append(details[i].getErrorMessage()); + } + } + buf.append("\n\nStacktrace:"); + throw new JasperException( + Localizer.getMessage("jsp.error.unable.compile") + ": " + buf); + } + + /** + * Processes the given javac error report and exception. + * + * @param errorReport Compilation error report + * @param exception Compilation exception + */ + public void javacError(String errorReport, Exception exception) + throws JasperException { + + throw new JasperException( + Localizer.getMessage("jsp.error.unable.compile"), exception); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java new file mode 100644 index 000000000..1925f55d3 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java @@ -0,0 +1,207 @@ +/* + * 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 org.xml.sax.Attributes; +import org.apache.jasper.JasperException; + +class Dumper { + + static class DumpVisitor extends Node.Visitor { + private int indent = 0; + + private String getAttributes(Attributes attrs) { + if (attrs == null) + return ""; + + StringBuffer buf = new StringBuffer(); + for (int i=0; i < attrs.getLength(); i++) { + buf.append(" " + attrs.getQName(i) + "=\"" + + attrs.getValue(i) + "\""); + } + return buf.toString(); + } + + private void printString(String str) { + printIndent(); + System.out.print(str); + } + + private void printString(String prefix, char[] chars, String suffix) { + String str = null; + if (chars != null) { + str = new String(chars); + } + printString(prefix, str, suffix); + } + + private void printString(String prefix, String str, String suffix) { + printIndent(); + if (str != null) { + System.out.print(prefix + str + suffix); + } else { + System.out.print(prefix + suffix); + } + } + + private void printAttributes(String prefix, Attributes attrs, + String suffix) { + printString(prefix, getAttributes(attrs), suffix); + } + + private void dumpBody(Node n) throws JasperException { + Node.Nodes page = n.getBody(); + if (page != null) { +// indent++; + page.visit(this); +// indent--; + } + } + + public void visit(Node.PageDirective n) throws JasperException { + printAttributes("<%@ page", n.getAttributes(), "%>"); + } + + public void visit(Node.TaglibDirective n) throws JasperException { + printAttributes("<%@ taglib", n.getAttributes(), "%>"); + } + + public void visit(Node.IncludeDirective n) throws JasperException { + printAttributes("<%@ include", n.getAttributes(), "%>"); + dumpBody(n); + } + + public void visit(Node.Comment n) throws JasperException { + printString("<%--", n.getText(), "--%>"); + } + + public void visit(Node.Declaration n) throws JasperException { + printString("<%!", n.getText(), "%>"); + } + + public void visit(Node.Expression n) throws JasperException { + printString("<%=", n.getText(), "%>"); + } + + public void visit(Node.Scriptlet n) throws JasperException { + printString("<%", n.getText(), "%>"); + } + + public void visit(Node.IncludeAction n) throws JasperException { + printAttributes(""); + dumpBody(n); + printString(""); + } + + public void visit(Node.ForwardAction n) throws JasperException { + printAttributes(""); + dumpBody(n); + printString(""); + } + + public void visit(Node.GetProperty n) throws JasperException { + printAttributes(""); + } + + public void visit(Node.SetProperty n) throws JasperException { + printAttributes(""); + dumpBody(n); + printString(""); + } + + public void visit(Node.UseBean n) throws JasperException { + printAttributes(""); + dumpBody(n); + printString(""); + } + + public void visit(Node.PlugIn n) throws JasperException { + printAttributes(""); + dumpBody(n); + printString("
"); + } + + public void visit(Node.ParamsAction n) throws JasperException { + printAttributes(""); + dumpBody(n); + printString(""); + } + + public void visit(Node.ParamAction n) throws JasperException { + printAttributes(""); + dumpBody(n); + printString(""); + } + + public void visit(Node.NamedAttribute n) throws JasperException { + printAttributes(""); + dumpBody(n); + printString(""); + } + + public void visit(Node.JspBody n) throws JasperException { + printAttributes(""); + dumpBody(n); + printString(""); + } + + public void visit(Node.ELExpression n) throws JasperException { + printString( "${" + new String( n.getText() ) + "}" ); + } + + public void visit(Node.CustomTag n) throws JasperException { + printAttributes("<" + n.getQName(), n.getAttributes(), ">"); + dumpBody(n); + printString(""); + } + + public void visit(Node.UninterpretedTag n) throws JasperException { + String tag = n.getQName(); + printAttributes("<"+tag, n.getAttributes(), ">"); + dumpBody(n); + printString(""); + } + + public void visit(Node.TemplateText n) throws JasperException { + printString(new String(n.getText())); + } + + private void printIndent() { + for (int i=0; i < indent; i++) { + System.out.print(" "); + } + } + } + + public static void dump(Node n) { + try { + n.accept(new DumpVisitor()); + } catch (JasperException e) { + e.printStackTrace(); + } + } + + public static void dump(Node.Nodes page) { + try { + page.visit(new DumpVisitor()); + } catch (JasperException e) { + e.printStackTrace(); + } + } +} + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java new file mode 100644 index 000000000..f2df33dc0 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java @@ -0,0 +1,281 @@ +/* + * 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.util.*; +import javax.servlet.jsp.tagext.FunctionInfo; +import org.apache.jasper.JasperException; + +/** + * This class generates functions mappers for the EL expressions in the page. + * Instead of a global mapper, a mapper is used for ecah call to EL + * evaluator, thus avoiding the prefix overlapping and redefinition + * issues. + * + * @author Kin-man Chung + */ + +public class ELFunctionMapper { + private int currFunc = 0; + StringBuffer ds; // Contains codes to initialize the functions mappers. + StringBuffer ss; // Contains declarations of the functions mappers. + + /** + * Creates the functions mappers for all EL expressions in the JSP page. + * + * @param compiler Current compiler, mainly for accessing error dispatcher. + * @param page The current compilation unit. + */ + public static void map(Compiler compiler, Node.Nodes page) + throws JasperException { + + ELFunctionMapper map = new ELFunctionMapper(); + map.ds = new StringBuffer(); + map.ss = new StringBuffer(); + + page.visit(map.new ELFunctionVisitor()); + + // Append the declarations to the root node + String ds = map.ds.toString(); + if (ds.length() > 0) { + Node root = page.getRoot(); + new Node.Declaration(map.ss.toString(), null, root); + new Node.Declaration("static {\n" + ds + "}\n", null, root); + } + } + + /** + * A visitor for the page. The places where EL is allowed are scanned + * for functions, and if found functions mappers are created. + */ + class ELFunctionVisitor extends Node.Visitor { + + /** + * Use a global name map to facilitate reuse of function maps. + * The key used is prefix:function:uri. + */ + private HashMap gMap = new HashMap(); + + public void visit(Node.ParamAction n) throws JasperException { + doMap(n.getValue()); + visitBody(n); + } + + public void visit(Node.IncludeAction n) throws JasperException { + doMap(n.getPage()); + visitBody(n); + } + + public void visit(Node.ForwardAction n) throws JasperException { + doMap(n.getPage()); + visitBody(n); + } + + public void visit(Node.SetProperty n) throws JasperException { + doMap(n.getValue()); + visitBody(n); + } + + public void visit(Node.UseBean n) throws JasperException { + doMap(n.getBeanName()); + visitBody(n); + } + + public void visit(Node.PlugIn n) throws JasperException { + doMap(n.getHeight()); + doMap(n.getWidth()); + visitBody(n); + } + + public void visit(Node.JspElement n) throws JasperException { + + Node.JspAttribute[] attrs = n.getJspAttributes(); + for (int i = 0; attrs != null && i < attrs.length; i++) { + doMap(attrs[i]); + } + doMap(n.getNameAttribute()); + visitBody(n); + } + + public void visit(Node.UninterpretedTag n) throws JasperException { + + Node.JspAttribute[] attrs = n.getJspAttributes(); + for (int i = 0; attrs != null && i < attrs.length; i++) { + doMap(attrs[i]); + } + visitBody(n); + } + + public void visit(Node.CustomTag n) throws JasperException { + Node.JspAttribute[] attrs = n.getJspAttributes(); + for (int i = 0; attrs != null && i < attrs.length; i++) { + doMap(attrs[i]); + } + visitBody(n); + } + + public void visit(Node.ELExpression n) throws JasperException { + doMap(n.getEL()); + } + + private void doMap(Node.JspAttribute attr) + throws JasperException { + if (attr != null) { + doMap(attr.getEL()); + } + } + + /** + * Creates function mappers, if needed, from ELNodes + */ + private void doMap(ELNode.Nodes el) + throws JasperException { + + // Only care about functions in ELNode's + class Fvisitor extends ELNode.Visitor { + ArrayList funcs = + new ArrayList(); + HashMap keyMap = new HashMap(); + public void visit(ELNode.Function n) throws JasperException { + String key = n.getPrefix() + ":" + n.getName(); + if (! keyMap.containsKey(key)) { + keyMap.put(key,""); + funcs.add(n); + } + } + } + + if (el == null) { + return; + } + + // First locate all unique functions in this EL + Fvisitor fv = new Fvisitor(); + el.visit(fv); + ArrayList functions = fv.funcs; + + if (functions.size() == 0) { + return; + } + + // Reuse a previous map if possible + String decName = matchMap(functions); + if (decName != null) { + el.setMapName(decName); + return; + } + + // Generate declaration for the map statically + decName = getMapName(); + ss.append("static private org.apache.jasper.runtime.ProtectedFunctionMapper " + decName + ";\n"); + + ds.append(" " + decName + "= "); + ds.append("org.apache.jasper.runtime.ProtectedFunctionMapper"); + + // Special case if there is only one function in the map + String funcMethod = null; + if (functions.size() == 1) { + funcMethod = ".getMapForFunction"; + } else { + ds.append(".getInstance();\n"); + funcMethod = " " + decName + ".mapFunction"; + } + + // Setup arguments for either getMapForFunction or mapFunction + for (int i = 0; i < functions.size(); i++) { + ELNode.Function f = (ELNode.Function)functions.get(i); + FunctionInfo funcInfo = f.getFunctionInfo(); + String key = f.getPrefix()+ ":" + f.getName(); + ds.append(funcMethod + "(\"" + key + "\", " + + funcInfo.getFunctionClass() + ".class, " + + '\"' + f.getMethodName() + "\", " + + "new Class[] {"); + String params[] = f.getParameters(); + for (int k = 0; k < params.length; k++) { + if (k != 0) { + ds.append(", "); + } + int iArray = params[k].indexOf('['); + if (iArray < 0) { + ds.append(params[k] + ".class"); + } + else { + String baseType = params[k].substring(0, iArray); + ds.append("java.lang.reflect.Array.newInstance("); + ds.append(baseType); + ds.append(".class,"); + + // Count the number of array dimension + int aCount = 0; + for (int jj = iArray; jj < params[k].length(); jj++ ) { + if (params[k].charAt(jj) == '[') { + aCount++; + } + } + if (aCount == 1) { + ds.append("0).getClass()"); + } else { + ds.append("new int[" + aCount + "]).getClass()"); + } + } + } + ds.append("});\n"); + // Put the current name in the global function map + gMap.put(f.getPrefix() + ':' + f.getName() + ':' + f.getUri(), + decName); + } + el.setMapName(decName); + } + + /** + * Find the name of the function mapper for an EL. Reuse a + * previously generated one if possible. + * @param functions An ArrayList of ELNode.Function instances that + * represents the functions in an EL + * @return A previous generated function mapper name that can be used + * by this EL; null if none found. + */ + private String matchMap(ArrayList functions) { + + String mapName = null; + for (int i = 0; i < functions.size(); i++) { + ELNode.Function f = (ELNode.Function)functions.get(i); + String temName = (String) gMap.get(f.getPrefix() + ':' + + f.getName() + ':' + f.getUri()); + if (temName == null) { + return null; + } + if (mapName == null) { + mapName = temName; + } else if (!temName.equals(mapName)) { + // If not all in the previous match, then no match. + return null; + } + } + return mapName; + } + + /* + * @return An unique name for a function mapper. + */ + private String getMapName() { + return "_jspx_fnmap_" + currFunc++; + } + } +} + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java new file mode 100644 index 000000000..3ee629e94 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java @@ -0,0 +1,255 @@ +/* + * 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.util.*; +import javax.servlet.jsp.tagext.FunctionInfo; +import org.apache.jasper.JasperException; + +/** + * This class defines internal representation for an EL Expression + * + * It currently only defines functions. It can be expanded to define + * all the components of an EL expression, if need to. + * + * @author Kin-man Chung + */ + +abstract class ELNode { + + abstract public void accept(Visitor v) throws JasperException; + + /** + * Child classes + */ + + + /** + * Represents an EL expression: anything in ${ and }. + */ + public static class Root extends ELNode { + + private ELNode.Nodes expr; + private char type; + + Root(ELNode.Nodes expr, char type) { + this.expr = expr; + this.type = type; + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public ELNode.Nodes getExpression() { + return expr; + } + + public char getType() { + return type; + } + } + + /** + * Represents text outside of EL expression. + */ + public static class Text extends ELNode { + + private String text; + + Text(String text) { + this.text = text; + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public String getText() { + return text; + } + } + + /** + * Represents anything in EL expression, other than functions, including + * function arguments etc + */ + public static class ELText extends ELNode { + + private String text; + + ELText(String text) { + this.text = text; + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public String getText() { + return text; + } + } + + /** + * Represents a function + * Currently only include the prefix and function name, but not its + * arguments. + */ + public static class Function extends ELNode { + + private String prefix; + private String name; + private String uri; + private FunctionInfo functionInfo; + private String methodName; + private String[] parameters; + + Function(String prefix, String name) { + this.prefix = prefix; + this.name = name; + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public String getPrefix() { + return prefix; + } + + public String getName() { + return name; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public String getUri() { + return uri; + } + + public void setFunctionInfo(FunctionInfo f) { + this.functionInfo = f; + } + + public FunctionInfo getFunctionInfo() { + return functionInfo; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public String getMethodName() { + return methodName; + } + + public void setParameters(String[] parameters) { + this.parameters = parameters; + } + + public String[] getParameters() { + return parameters; + } + } + + /** + * An ordered list of ELNode. + */ + public static class Nodes { + + /* Name used for creating a map for the functions in this + EL expression, for communication to Generator. + */ + String mapName = null; // The function map associated this EL + private List list; + + public Nodes() { + list = new ArrayList(); + } + + public void add(ELNode en) { + list.add(en); + } + + /** + * Visit the nodes in the list with the supplied visitor + * @param v The visitor used + */ + public void visit(Visitor v) throws JasperException { + Iterator iter = list.iterator(); + while (iter.hasNext()) { + ELNode n = iter.next(); + n.accept(v); + } + } + + public Iterator iterator() { + return list.iterator(); + } + + public boolean isEmpty() { + return list.size() == 0; + } + + /** + * @return true if the expression contains a ${...} + */ + public boolean containsEL() { + Iterator iter = list.iterator(); + while (iter.hasNext()) { + ELNode n = iter.next(); + if (n instanceof Root) { + return true; + } + } + return false; + } + + public void setMapName(String name) { + this.mapName = name; + } + + public String getMapName() { + return mapName; + } + + } + + /* + * A visitor class for traversing ELNodes + */ + public static class Visitor { + + public void visit(Root n) throws JasperException { + n.getExpression().visit(this); + } + + public void visit(Function n) throws JasperException { + } + + public void visit(Text n) throws JasperException { + } + + public void visit(ELText n) throws JasperException { + } + } +} + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java new file mode 100644 index 000000000..e8cba0221 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java @@ -0,0 +1,382 @@ +/* + * 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; + +/** + * This class implements a parser for EL expressions. + * + * It takes strings of the form xxx${..}yyy${..}zzz etc, and turn it into a + * ELNode.Nodes. + * + * Currently, it only handles text outside ${..} and functions in ${ ..}. + * + * @author Kin-man Chung + */ + +public class ELParser { + + private Token curToken; // current token + + private ELNode.Nodes expr; + + private ELNode.Nodes ELexpr; + + private int index; // Current index of the expression + + private String expression; // The EL expression + + private char type; + + private boolean escapeBS; // is '\' an escape char in text outside EL? + + private static final String reservedWords[] = { "and", "div", "empty", + "eq", "false", "ge", "gt", "instanceof", "le", "lt", "mod", "ne", + "not", "null", "or", "true" }; + + public ELParser(String expression) { + index = 0; + this.expression = expression; + expr = new ELNode.Nodes(); + } + + /** + * Parse an EL expression + * + * @param expression + * The input expression string of the form Char* ('${' Char* + * '}')* Char* + * @return Parsed EL expression in ELNode.Nodes + */ + public static ELNode.Nodes parse(String expression) { + ELParser parser = new ELParser(expression); + while (parser.hasNextChar()) { + String text = parser.skipUntilEL(); + if (text.length() > 0) { + parser.expr.add(new ELNode.Text(text)); + } + ELNode.Nodes elexpr = parser.parseEL(); + if (!elexpr.isEmpty()) { + parser.expr.add(new ELNode.Root(elexpr, parser.type)); + } + } + return parser.expr; + } + + /** + * Parse an EL expression string '${...}' + * + * @return An ELNode.Nodes representing the EL expression TODO: Currently + * only parsed into functions and text strings. This should be + * rewritten for a full parser. + */ + private ELNode.Nodes parseEL() { + + StringBuffer buf = new StringBuffer(); + ELexpr = new ELNode.Nodes(); + while (hasNext()) { + curToken = nextToken(); + if (curToken instanceof Char) { + if (curToken.toChar() == '}') { + break; + } + buf.append(curToken.toChar()); + } else { + // Output whatever is in buffer + if (buf.length() > 0) { + ELexpr.add(new ELNode.ELText(buf.toString())); + } + if (!parseFunction()) { + ELexpr.add(new ELNode.ELText(curToken.toString())); + } + } + } + if (buf.length() > 0) { + ELexpr.add(new ELNode.ELText(buf.toString())); + } + + return ELexpr; + } + + /** + * Parse for a function FunctionInvokation ::= (identifier ':')? identifier + * '(' (Expression (,Expression)*)? ')' Note: currently we don't parse + * arguments + */ + private boolean parseFunction() { + if (!(curToken instanceof Id) || isELReserved(curToken.toString())) { + return false; + } + String s1 = null; // Function prefix + String s2 = curToken.toString(); // Function name + int mark = getIndex(); + if (hasNext()) { + Token t = nextToken(); + if (t.toChar() == ':') { + if (hasNext()) { + Token t2 = nextToken(); + if (t2 instanceof Id) { + s1 = s2; + s2 = t2.toString(); + if (hasNext()) { + t = nextToken(); + } + } + } + } + if (t.toChar() == '(') { + ELexpr.add(new ELNode.Function(s1, s2)); + return true; + } + } + setIndex(mark); + return false; + } + + /** + * Test if an id is a reserved word in EL + */ + private boolean isELReserved(String id) { + int i = 0; + int j = reservedWords.length; + while (i < j) { + int k = (i + j) / 2; + int result = reservedWords[k].compareTo(id); + if (result == 0) { + return true; + } + if (result < 0) { + i = k + 1; + } else { + j = k; + } + } + return false; + } + + /** + * Skip until an EL expression ('${' || '#{') is reached, allowing escape + * sequences '\\' and '\$' and '\#'. + * + * @return The text string up to the EL expression + */ + private String skipUntilEL() { + char prev = 0; + StringBuffer buf = new StringBuffer(); + while (hasNextChar()) { + char ch = nextChar(); + if (prev == '\\') { + prev = 0; + if (ch == '\\') { + buf.append('\\'); + if (!escapeBS) + prev = '\\'; + } else if (ch == '$' || ch == '#') { + buf.append(ch); + } + // else error! + } else if (prev == '$' || prev == '#') { + if (ch == '{') { + this.type = prev; + prev = 0; + break; + } + buf.append(prev); + prev = 0; + } + if (ch == '\\' || ch == '$' || ch == '#') { + prev = ch; + } else { + buf.append(ch); + } + } + if (prev != 0) { + buf.append(prev); + } + return buf.toString(); + } + + /* + * @return true if there is something left in EL expression buffer other + * than white spaces. + */ + private boolean hasNext() { + skipSpaces(); + return hasNextChar(); + } + + /* + * @return The next token in the EL expression buffer. + */ + private Token nextToken() { + skipSpaces(); + if (hasNextChar()) { + char ch = nextChar(); + if (Character.isJavaIdentifierStart(ch)) { + StringBuffer buf = new StringBuffer(); + buf.append(ch); + while ((ch = peekChar()) != -1 + && Character.isJavaIdentifierPart(ch)) { + buf.append(ch); + nextChar(); + } + return new Id(buf.toString()); + } + + if (ch == '\'' || ch == '"') { + return parseQuotedChars(ch); + } else { + // For now... + return new Char(ch); + } + } + return null; + } + + /* + * Parse a string in single or double quotes, allowing for escape sequences + * '\\', and ('\"', or "\'") + */ + private Token parseQuotedChars(char quote) { + StringBuffer buf = new StringBuffer(); + buf.append(quote); + while (hasNextChar()) { + char ch = nextChar(); + if (ch == '\\') { + ch = nextChar(); + if (ch == '\\' || ch == quote) { + buf.append(ch); + } + // else error! + } else if (ch == quote) { + buf.append(ch); + break; + } else { + buf.append(ch); + } + } + return new QuotedString(buf.toString()); + } + + /* + * A collection of low level parse methods dealing with character in the EL + * expression buffer. + */ + + private void skipSpaces() { + while (hasNextChar()) { + if (expression.charAt(index) > ' ') + break; + index++; + } + } + + private boolean hasNextChar() { + return index < expression.length(); + } + + private char nextChar() { + if (index >= expression.length()) { + return (char) -1; + } + return expression.charAt(index++); + } + + private char peekChar() { + if (index >= expression.length()) { + return (char) -1; + } + return expression.charAt(index); + } + + private int getIndex() { + return index; + } + + private void setIndex(int i) { + index = i; + } + + /* + * Represents a token in EL expression string + */ + private static class Token { + + char toChar() { + return 0; + } + + public String toString() { + return ""; + } + } + + /* + * Represents an ID token in EL + */ + private static class Id extends Token { + String id; + + Id(String id) { + this.id = id; + } + + public String toString() { + return id; + } + } + + /* + * Represents a character token in EL + */ + private static class Char extends Token { + + private char ch; + + Char(char ch) { + this.ch = ch; + } + + char toChar() { + return ch; + } + + public String toString() { + return (new Character(ch)).toString(); + } + } + + /* + * Represents a quoted (single or double) string token in EL + */ + private static class QuotedString extends Token { + + private String value; + + QuotedString(String v) { + this.value = v; + } + + public String toString() { + return value; + } + } + + public char getType() { + return type; + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ErrorDispatcher.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ErrorDispatcher.java new file mode 100644 index 000000000..b4872c4d3 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ErrorDispatcher.java @@ -0,0 +1,615 @@ +/* + * 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.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.util.ArrayList; + +import org.apache.jasper.JasperException; +import org.apache.jasper.JspCompilationContext; +import org.xml.sax.SAXException; + +/** + * Class responsible for dispatching JSP parse and javac compilation errors + * to the configured error handler. + * + * This class is also responsible for localizing any error codes before they + * are passed on to the configured error handler. + * + * In the case of a Java compilation error, the compiler error message is + * parsed into an array of JavacErrorDetail instances, which is passed on to + * the configured error handler. + * + * @author Jan Luehe + * @author Kin-man Chung + */ +public class ErrorDispatcher { + + // Custom error handler + private ErrorHandler errHandler; + + // Indicates whether the compilation was initiated by JspServlet or JspC + private boolean jspcMode = false; + + + /* + * Constructor. + * + * @param jspcMode true if compilation has been initiated by JspC, false + * otherwise + */ + public ErrorDispatcher(boolean jspcMode) { + // XXX check web.xml for custom error handler + errHandler = new DefaultErrorHandler(); + this.jspcMode = jspcMode; + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param errCode Error code + */ + public void jspError(String errCode) throws JasperException { + dispatch(null, errCode, null, null); + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param where Error location + * @param errCode Error code + */ + public void jspError(Mark where, String errCode) throws JasperException { + dispatch(where, errCode, null, null); + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param n Node that caused the error + * @param errCode Error code + */ + public void jspError(Node n, String errCode) throws JasperException { + dispatch(n.getStart(), errCode, null, null); + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param errCode Error code + * @param arg Argument for parametric replacement + */ + public void jspError(String errCode, String arg) throws JasperException { + dispatch(null, errCode, new Object[] {arg}, null); + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param where Error location + * @param errCode Error code + * @param arg Argument for parametric replacement + */ + public void jspError(Mark where, String errCode, String arg) + throws JasperException { + dispatch(where, errCode, new Object[] {arg}, null); + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param n Node that caused the error + * @param errCode Error code + * @param arg Argument for parametric replacement + */ + public void jspError(Node n, String errCode, String arg) + throws JasperException { + dispatch(n.getStart(), errCode, new Object[] {arg}, null); + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param errCode Error code + * @param arg1 First argument for parametric replacement + * @param arg2 Second argument for parametric replacement + */ + public void jspError(String errCode, String arg1, String arg2) + throws JasperException { + dispatch(null, errCode, new Object[] {arg1, arg2}, null); + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param errCode Error code + * @param arg1 First argument for parametric replacement + * @param arg2 Second argument for parametric replacement + * @param arg3 Third argument for parametric replacement + */ + public void jspError(String errCode, String arg1, String arg2, String arg3) + throws JasperException { + dispatch(null, errCode, new Object[] {arg1, arg2, arg3}, null); + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param where Error location + * @param errCode Error code + * @param arg1 First argument for parametric replacement + * @param arg2 Second argument for parametric replacement + */ + public void jspError(Mark where, String errCode, String arg1, String arg2) + throws JasperException { + dispatch(where, errCode, new Object[] {arg1, arg2}, null); + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param where Error location + * @param errCode Error code + * @param arg1 First argument for parametric replacement + * @param arg2 Second argument for parametric replacement + * @param arg3 Third argument for parametric replacement + */ + + public void jspError(Mark where, String errCode, String arg1, String arg2, + String arg3) + throws JasperException { + dispatch(where, errCode, new Object[] {arg1, arg2, arg3}, null); + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param n Node that caused the error + * @param errCode Error code + * @param arg1 First argument for parametric replacement + * @param arg2 Second argument for parametric replacement + */ + + public void jspError(Node n, String errCode, String arg1, String arg2) + throws JasperException { + dispatch(n.getStart(), errCode, new Object[] {arg1, arg2}, null); + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param n Node that caused the error + * @param errCode Error code + * @param arg1 First argument for parametric replacement + * @param arg2 Second argument for parametric replacement + * @param arg3 Third argument for parametric replacement + */ + + public void jspError(Node n, String errCode, String arg1, String arg2, + String arg3) + throws JasperException { + dispatch(n.getStart(), errCode, new Object[] {arg1, arg2, arg3}, null); + } + + /* + * Dispatches the given parsing exception to the configured error handler. + * + * @param e Parsing exception + */ + public void jspError(Exception e) throws JasperException { + dispatch(null, null, null, e); + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param errCode Error code + * @param arg Argument for parametric replacement + * @param e Parsing exception + */ + public void jspError(String errCode, String arg, Exception e) + throws JasperException { + dispatch(null, errCode, new Object[] {arg}, e); + } + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param n Node that caused the error + * @param errCode Error code + * @param arg Argument for parametric replacement + * @param e Parsing exception + */ + public void jspError(Node n, String errCode, String arg, Exception e) + throws JasperException { + dispatch(n.getStart(), errCode, new Object[] {arg}, e); + } + + /** + * Parses the given error message into an array of javac compilation error + * messages (one per javac compilation error line number). + * + * @param errMsg Error message + * @param fname Name of Java source file whose compilation failed + * @param page Node representation of JSP page from which the Java source + * file was generated + * + * @return Array of javac compilation errors, or null if the given error + * message does not contain any compilation error line numbers + */ + public static JavacErrorDetail[] parseJavacErrors(String errMsg, + String fname, + Node.Nodes page) + throws JasperException, IOException { + + return parseJavacMessage(errMsg, fname, page); + } + + /* + * Dispatches the given javac compilation errors to the configured error + * handler. + * + * @param javacErrors Array of javac compilation errors + */ + public void javacError(JavacErrorDetail[] javacErrors) + throws JasperException { + + errHandler.javacError(javacErrors); + } + + + /* + * Dispatches the given compilation error report and exception to the + * configured error handler. + * + * @param errorReport Compilation error report + * @param e Compilation exception + */ + public void javacError(String errorReport, Exception e) + throws JasperException { + + errHandler.javacError(errorReport, e); + } + + + //********************************************************************* + // Private utility methods + + /* + * Dispatches the given JSP parse error to the configured error handler. + * + * The given error code is localized. If it is not found in the + * resource bundle for localized error messages, it is used as the error + * message. + * + * @param where Error location + * @param errCode Error code + * @param args Arguments for parametric replacement + * @param e Parsing exception + */ + private void dispatch(Mark where, String errCode, Object[] args, + Exception e) throws JasperException { + String file = null; + String errMsg = null; + int line = -1; + int column = -1; + boolean hasLocation = false; + + // Localize + if (errCode != null) { + errMsg = Localizer.getMessage(errCode, args); + } else if (e != null) { + // give a hint about what's wrong + errMsg = e.getMessage(); + } + + // Get error location + if (where != null) { + if (jspcMode) { + // Get the full URL of the resource that caused the error + try { + file = where.getURL().toString(); + } catch (MalformedURLException me) { + // Fallback to using context-relative path + file = where.getFile(); + } + } else { + // Get the context-relative resource path, so as to not + // disclose any local filesystem details + file = where.getFile(); + } + line = where.getLineNumber(); + column = where.getColumnNumber(); + hasLocation = true; + } + + // Get nested exception + Exception nestedEx = e; + if ((e instanceof SAXException) + && (((SAXException) e).getException() != null)) { + nestedEx = ((SAXException) e).getException(); + } + + if (hasLocation) { + errHandler.jspError(file, line, column, errMsg, nestedEx); + } else { + errHandler.jspError(errMsg, nestedEx); + } + } + + /* + * Parses the given Java compilation error message, which may contain one + * or more compilation errors, into an array of JavacErrorDetail instances. + * + * Each JavacErrorDetail instance contains the information about a single + * compilation error. + * + * @param errMsg Compilation error message that was generated by the + * javac compiler + * @param fname Name of Java source file whose compilation failed + * @param page Node representation of JSP page from which the Java source + * file was generated + * + * @return Array of JavacErrorDetail instances corresponding to the + * compilation errors + */ + private static JavacErrorDetail[] parseJavacMessage( + String errMsg, String fname, Node.Nodes page) + throws IOException, JasperException { + + ArrayList errors = new ArrayList(); + StringBuffer errMsgBuf = null; + int lineNum = -1; + JavacErrorDetail javacError = null; + + BufferedReader reader = new BufferedReader(new StringReader(errMsg)); + + /* + * Parse compilation errors. Each compilation error consists of a file + * path and error line number, followed by a number of lines describing + * the error. + */ + String line = null; + while ((line = reader.readLine()) != null) { + + /* + * Error line number is delimited by set of colons. + * Ignore colon following drive letter on Windows (fromIndex = 2). + * XXX Handle deprecation warnings that don't have line info + */ + int beginColon = line.indexOf(':', 2); + int endColon = line.indexOf(':', beginColon + 1); + if ((beginColon >= 0) && (endColon >= 0)) { + if (javacError != null) { + // add previous error to error vector + errors.add(javacError); + } + + String lineNumStr = line.substring(beginColon + 1, endColon); + try { + lineNum = Integer.parseInt(lineNumStr); + } catch (NumberFormatException e) { + lineNum = -1; + } + + errMsgBuf = new StringBuffer(); + + javacError = createJavacError(fname, page, errMsgBuf, lineNum); + } + + // Ignore messages preceding first error + if (errMsgBuf != null) { + errMsgBuf.append(line); + errMsgBuf.append("\n"); + } + } + + // Add last error to error vector + if (javacError != null) { + errors.add(javacError); + } + + reader.close(); + + JavacErrorDetail[] errDetails = null; + if (errors.size() > 0) { + errDetails = new JavacErrorDetail[errors.size()]; + errors.toArray(errDetails); + } + + return errDetails; + } + + + /** + * @param fname + * @param page + * @param errMsgBuf + * @param lineNum + * @return JavacErrorDetail The error details + * @throws JasperException + */ + public static JavacErrorDetail createJavacError(String fname, + Node.Nodes page, StringBuffer errMsgBuf, int lineNum) + throws JasperException { + return createJavacError(fname, page, errMsgBuf, lineNum, null); + } + + + /** + * @param fname + * @param page + * @param errMsgBuf + * @param lineNum + * @param ctxt + * @return JavacErrorDetail The error details + * @throws JasperException + */ + public static JavacErrorDetail createJavacError(String fname, + Node.Nodes page, StringBuffer errMsgBuf, int lineNum, + JspCompilationContext ctxt) throws JasperException { + JavacErrorDetail javacError; + // Attempt to map javac error line number to line in JSP page + ErrorVisitor errVisitor = new ErrorVisitor(lineNum); + page.visit(errVisitor); + Node errNode = errVisitor.getJspSourceNode(); + if ((errNode != null) && (errNode.getStart() != null)) { + // If this is a scriplet node then there is a one to one mapping + // between JSP lines and Java lines + if (errVisitor.getJspSourceNode() instanceof Node.Scriptlet) { + javacError = new JavacErrorDetail( + fname, + lineNum, + errNode.getStart().getFile(), + errNode.getStart().getLineNumber() + lineNum - + errVisitor.getJspSourceNode().getBeginJavaLine(), + errMsgBuf, + ctxt); + } else { + javacError = new JavacErrorDetail( + fname, + lineNum, + errNode.getStart().getFile(), + errNode.getStart().getLineNumber(), + errMsgBuf, + ctxt); + } + } else { + /* + * javac error line number cannot be mapped to JSP page + * line number. For example, this is the case if a + * scriptlet is missing a closing brace, which causes + * havoc with the try-catch-finally block that the code + * generator places around all generated code: As a result + * of this, the javac error line numbers will be outside + * the range of begin and end java line numbers that were + * generated for the scriptlet, and therefore cannot be + * mapped to the start line number of the scriptlet in the + * JSP page. + * Include just the javac error info in the error detail. + */ + javacError = new JavacErrorDetail( + fname, + lineNum, + errMsgBuf); + } + return javacError; + } + + + /* + * Visitor responsible for mapping a line number in the generated servlet + * source code to the corresponding JSP node. + */ + static class ErrorVisitor extends Node.Visitor { + + // Java source line number to be mapped + private int lineNum; + + /* + * JSP node whose Java source code range in the generated servlet + * contains the Java source line number to be mapped + */ + Node found; + + /* + * Constructor. + * + * @param lineNum Source line number in the generated servlet code + */ + public ErrorVisitor(int lineNum) { + this.lineNum = lineNum; + } + + public void doVisit(Node n) throws JasperException { + if ((lineNum >= n.getBeginJavaLine()) + && (lineNum < n.getEndJavaLine())) { + found = n; + } + } + + /* + * Gets the JSP node to which the source line number in the generated + * servlet code was mapped. + * + * @return JSP node to which the source line number in the generated + * servlet code was mapped + */ + public Node getJspSourceNode() { + return found; + } + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ErrorHandler.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ErrorHandler.java new file mode 100644 index 000000000..a998edaf0 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ErrorHandler.java @@ -0,0 +1,73 @@ +/* + * 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 org.apache.jasper.JasperException; + +/** + * Interface for handling JSP parse and javac compilation errors. + * + * An implementation of this interface may be registered with the + * ErrorDispatcher by setting the XXX initialization parameter in the JSP + * page compiler and execution servlet in Catalina's web.xml file to the + * implementation's fully qualified class name. + * + * @author Jan Luehe + * @author Kin-man Chung + */ +public interface ErrorHandler { + + /** + * Processes the given JSP parse error. + * + * @param fname Name of the JSP file in which the parse error occurred + * @param line Parse error line number + * @param column Parse error column number + * @param msg Parse error message + * @param exception Parse exception + */ + public void jspError(String fname, int line, int column, String msg, + Exception exception) throws JasperException; + + /** + * Processes the given JSP parse error. + * + * @param msg Parse error message + * @param exception Parse exception + */ + public void jspError(String msg, Exception exception) + throws JasperException; + + /** + * Processes the given javac compilation errors. + * + * @param details Array of JavacErrorDetail instances corresponding to the + * compilation errors + */ + public void javacError(JavacErrorDetail[] details) + throws JasperException; + + /** + * Processes the given javac error report and exception. + * + * @param errorReport Compilation error report + * @param exception Compilation exception + */ + public void javacError(String errorReport, Exception exception) + throws JasperException; +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Generator.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Generator.java new file mode 100644 index 000000000..59c2d27e6 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Generator.java @@ -0,0 +1,4199 @@ +/* + * 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.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; + +import javax.el.MethodExpression; +import javax.el.ValueExpression; +import javax.servlet.jsp.tagext.TagAttributeInfo; +import javax.servlet.jsp.tagext.TagInfo; +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.compiler.Node.NamedAttribute; +import org.apache.jasper.runtime.JspRuntimeLibrary; +import org.xml.sax.Attributes; + +/** + * Generate Java source from Nodes + * + * @author Anil K. Vijendran + * @author Danno Ferrin + * @author Mandar Raje + * @author Rajiv Mordani + * @author Pierre Delisle + * + * Tomcat 4.1.x and Tomcat 5: + * @author Kin-man Chung + * @author Jan Luehe + * @author Shawn Bayern + * @author Mark Roth + * @author Denis Benoit + * + * Tomcat 6.x + * @author Jacob Hookom + * @author Remy Maucherat + */ + +class Generator { + + private static final Class[] OBJECT_CLASS = { Object.class }; + + private static final String VAR_EXPRESSIONFACTORY = + System.getProperty("org.apache.jasper.compiler.Generator.VAR_EXPRESSIONFACTORY", "_el_expressionfactory"); + private static final String VAR_ANNOTATIONPROCESSOR = + System.getProperty("org.apache.jasper.compiler.Generator.VAR_ANNOTATIONPROCESSOR", "_jsp_annotationprocessor"); + + private ServletWriter out; + + private ArrayList methodsBuffered; + + private FragmentHelperClass fragmentHelperClass; + + private ErrorDispatcher err; + + private BeanRepository beanInfo; + + private JspCompilationContext ctxt; + + private boolean isPoolingEnabled; + + private boolean breakAtLF; + + private String jspIdPrefix; + + private int jspId; + + private PageInfo pageInfo; + + private Vector tagHandlerPoolNames; + + private GenBuffer charArrayBuffer; + + /** + * @param s + * the input string + * @return quoted and escaped string, per Java rule + */ + static String quote(String s) { + + if (s == null) + return "null"; + + return '"' + escape(s) + '"'; + } + + /** + * @param s + * the input string + * @return escaped string, per Java rule + */ + static String escape(String s) { + + if (s == null) + return ""; + + StringBuffer b = new StringBuffer(); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == '"') + b.append('\\').append('"'); + else if (c == '\\') + b.append('\\').append('\\'); + else if (c == '\n') + b.append('\\').append('n'); + else if (c == '\r') + b.append('\\').append('r'); + else + b.append(c); + } + return b.toString(); + } + + /** + * Single quote and escape a character + */ + static String quote(char c) { + + StringBuffer b = new StringBuffer(); + b.append('\''); + if (c == '\'') + b.append('\\').append('\''); + else if (c == '\\') + b.append('\\').append('\\'); + else if (c == '\n') + b.append('\\').append('n'); + else if (c == '\r') + b.append('\\').append('r'); + else + b.append(c); + b.append('\''); + return b.toString(); + } + + private String createJspId() throws JasperException { + if (this.jspIdPrefix == null) { + StringBuffer sb = new StringBuffer(32); + String name = ctxt.getServletJavaFileName(); + sb.append("jsp_").append(Math.abs(name.hashCode())).append('_'); + this.jspIdPrefix = sb.toString(); + } + return this.jspIdPrefix + (this.jspId++); + } + + /** + * Generates declarations. This includes "info" of the page directive, and + * scriptlet declarations. + */ + private void generateDeclarations(Node.Nodes page) throws JasperException { + + class DeclarationVisitor extends Node.Visitor { + + private boolean getServletInfoGenerated = false; + + /* + * Generates getServletInfo() method that returns the value of the + * page directive's 'info' attribute, if present. + * + * The Validator has already ensured that if the translation unit + * contains more than one page directive with an 'info' attribute, + * their values match. + */ + public void visit(Node.PageDirective n) throws JasperException { + + if (getServletInfoGenerated) { + return; + } + + String info = n.getAttributeValue("info"); + if (info == null) + return; + + getServletInfoGenerated = true; + out.printil("public String getServletInfo() {"); + out.pushIndent(); + out.printin("return "); + out.print(quote(info)); + out.println(";"); + out.popIndent(); + out.printil("}"); + out.println(); + } + + public void visit(Node.Declaration n) throws JasperException { + n.setBeginJavaLine(out.getJavaLine()); + out.printMultiLn(new String(n.getText())); + out.println(); + n.setEndJavaLine(out.getJavaLine()); + } + + // Custom Tags may contain declarations from tag plugins. + public void visit(Node.CustomTag n) throws JasperException { + if (n.useTagPlugin()) { + if (n.getAtSTag() != null) { + n.getAtSTag().visit(this); + } + visitBody(n); + if (n.getAtETag() != null) { + n.getAtETag().visit(this); + } + } else { + visitBody(n); + } + } + } + + out.println(); + page.visit(new DeclarationVisitor()); + } + + /** + * Compiles list of tag handler pool names. + */ + private void compileTagHandlerPoolList(Node.Nodes page) + throws JasperException { + + class TagHandlerPoolVisitor extends Node.Visitor { + + private Vector names; + + /* + * Constructor + * + * @param v Vector of tag handler pool names to populate + */ + TagHandlerPoolVisitor(Vector v) { + names = v; + } + + /* + * Gets the name of the tag handler pool for the given custom tag + * and adds it to the list of tag handler pool names unless it is + * already contained in it. + */ + public void visit(Node.CustomTag n) throws JasperException { + + if (!n.implementsSimpleTag()) { + String name = createTagHandlerPoolName(n.getPrefix(), n + .getLocalName(), n.getAttributes(), + n.getNamedAttributeNodes(), n.hasEmptyBody()); + n.setTagHandlerPoolName(name); + if (!names.contains(name)) { + names.add(name); + } + } + visitBody(n); + } + + /* + * Creates the name of the tag handler pool whose tag handlers may + * be (re)used to service this action. + * + * @return The name of the tag handler pool + */ + private String createTagHandlerPoolName(String prefix, + String shortName, Attributes attrs, Node.Nodes namedAttrs, + boolean hasEmptyBody) { + String poolName = null; + + poolName = "_jspx_tagPool_" + prefix + "_" + shortName; + if (attrs != null) { + String[] attrNames = + new String[attrs.getLength() + namedAttrs.size()]; + for (int i = 0; i < attrNames.length; i++) { + attrNames[i] = attrs.getQName(i); + } + for (int i = 0; i < namedAttrs.size(); i++) { + attrNames[attrs.getLength() + i] = + ((NamedAttribute) namedAttrs.getNode(i)).getQName(); + } + Arrays.sort(attrNames, Collections.reverseOrder()); + if (attrNames.length > 0) { + poolName = poolName + "&"; + } + for (int i = 0; i < attrNames.length; i++) { + poolName = poolName + "_" + attrNames[i]; + } + } + if (hasEmptyBody) { + poolName = poolName + "_nobody"; + } + return JspUtil.makeJavaIdentifier(poolName); + } + } + + page.visit(new TagHandlerPoolVisitor(tagHandlerPoolNames)); + } + + private void declareTemporaryScriptingVars(Node.Nodes page) + throws JasperException { + + class ScriptingVarVisitor extends Node.Visitor { + + private Vector vars; + + ScriptingVarVisitor() { + vars = new Vector(); + } + + public void visit(Node.CustomTag n) throws JasperException { + + if (n.getCustomNestingLevel() > 0) { + TagVariableInfo[] tagVarInfos = n.getTagVariableInfos(); + VariableInfo[] varInfos = n.getVariableInfos(); + + if (varInfos.length > 0) { + for (int i = 0; i < varInfos.length; i++) { + String varName = varInfos[i].getVarName(); + String tmpVarName = "_jspx_" + varName + "_" + + n.getCustomNestingLevel(); + if (!vars.contains(tmpVarName)) { + vars.add(tmpVarName); + out.printin(varInfos[i].getClassName()); + out.print(" "); + out.print(tmpVarName); + out.print(" = "); + out.print(null); + out.println(";"); + } + } + } else { + for (int i = 0; i < tagVarInfos.length; i++) { + String varName = tagVarInfos[i].getNameGiven(); + if (varName == null) { + varName = n.getTagData().getAttributeString( + tagVarInfos[i].getNameFromAttribute()); + } else if (tagVarInfos[i].getNameFromAttribute() != null) { + // alias + continue; + } + String tmpVarName = "_jspx_" + varName + "_" + + n.getCustomNestingLevel(); + if (!vars.contains(tmpVarName)) { + vars.add(tmpVarName); + out.printin(tagVarInfos[i].getClassName()); + out.print(" "); + out.print(tmpVarName); + out.print(" = "); + out.print(null); + out.println(";"); + } + } + } + } + + visitBody(n); + } + } + + page.visit(new ScriptingVarVisitor()); + } + + /** + * Generates the _jspInit() method for instantiating the tag handler pools. + * For tag file, _jspInit has to be invoked manually, and the ServletConfig + * object explicitly passed. + * + * In JSP 2.1, we also instantiate an ExpressionFactory + */ + private void generateInit() { + + if (ctxt.isTagFile()) { + out.printil("private void _jspInit(ServletConfig config) {"); + } else { + out.printil("public void _jspInit() {"); + } + + out.pushIndent(); + if (isPoolingEnabled) { + for (int i = 0; i < tagHandlerPoolNames.size(); i++) { + out.printin(tagHandlerPoolNames.elementAt(i)); + out.print(" = org.apache.jasper.runtime.TagHandlerPool.getTagHandlerPool("); + if (ctxt.isTagFile()) { + out.print("config"); + } else { + out.print("getServletConfig()"); + } + out.println(");"); + } + } + + out.printin(VAR_EXPRESSIONFACTORY); + out.print(" = _jspxFactory.getJspApplicationContext("); + if (ctxt.isTagFile()) { + out.print("config"); + } else { + out.print("getServletConfig()"); + } + out.println(".getServletContext()).getExpressionFactory();"); + + out.printin(VAR_ANNOTATIONPROCESSOR); + out.print(" = (org.apache.AnnotationProcessor) "); + if (ctxt.isTagFile()) { + out.print("config"); + } else { + out.print("getServletConfig()"); + } + out.println(".getServletContext().getAttribute(org.apache.AnnotationProcessor.class.getName());"); + + out.popIndent(); + out.printil("}"); + out.println(); + } + + /** + * Generates the _jspDestroy() method which is responsible for calling the + * release() method on every tag handler in any of the tag handler pools. + */ + private void generateDestroy() { + + out.printil("public void _jspDestroy() {"); + out.pushIndent(); + + if (isPoolingEnabled) { + for (int i = 0; i < tagHandlerPoolNames.size(); i++) { + out.printin((String) tagHandlerPoolNames.elementAt(i)); + out.println(".release();"); + } + } + + out.popIndent(); + out.printil("}"); + out.println(); + } + + /** + * Generate preamble package name (shared by servlet and tag handler + * preamble generation) + */ + private void genPreamblePackage(String packageName) throws JasperException { + if (!"".equals(packageName) && packageName != null) { + out.printil("package " + packageName + ";"); + out.println(); + } + } + + /** + * Generate preamble imports (shared by servlet and tag handler preamble + * generation) + */ + private void genPreambleImports() throws JasperException { + Iterator iter = pageInfo.getImports().iterator(); + while (iter.hasNext()) { + out.printin("import "); + out.print((String) iter.next()); + out.println(";"); + } + + out.println(); + } + + /** + * Generation of static initializers in preamble. For example, dependant + * list, el function map, prefix map. (shared by servlet and tag handler + * preamble generation) + */ + private void genPreambleStaticInitializers() throws JasperException { + out.printil("private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();"); + out.println(); + + // Static data for getDependants() + out.printil("private static java.util.List _jspx_dependants;"); + out.println(); + List dependants = pageInfo.getDependants(); + Iterator iter = dependants.iterator(); + if (!dependants.isEmpty()) { + out.printil("static {"); + out.pushIndent(); + out.printin("_jspx_dependants = new java.util.ArrayList("); + out.print("" + dependants.size()); + out.println(");"); + while (iter.hasNext()) { + out.printin("_jspx_dependants.add(\""); + out.print((String) iter.next()); + out.println("\");"); + } + out.popIndent(); + out.printil("}"); + out.println(); + } + } + + /** + * Declare tag handler pools (tags of the same type and with the same + * attribute set share the same tag handler pool) (shared by servlet and tag + * handler preamble generation) + * + * In JSP 2.1, we also scope an instance of ExpressionFactory + */ + private void genPreambleClassVariableDeclarations(String className) + throws JasperException { + if (isPoolingEnabled && !tagHandlerPoolNames.isEmpty()) { + for (int i = 0; i < tagHandlerPoolNames.size(); i++) { + out.printil("private org.apache.jasper.runtime.TagHandlerPool " + + tagHandlerPoolNames.elementAt(i) + ";"); + } + out.println(); + } + out.printin("private javax.el.ExpressionFactory "); + out.print(VAR_EXPRESSIONFACTORY); + out.println(";"); + out.printin("private org.apache.AnnotationProcessor "); + out.print(VAR_ANNOTATIONPROCESSOR); + out.println(";"); + out.println(); + } + + /** + * Declare general-purpose methods (shared by servlet and tag handler + * preamble generation) + */ + private void genPreambleMethods() throws JasperException { + // Method used to get compile time file dependencies + out.printil("public Object getDependants() {"); + out.pushIndent(); + out.printil("return _jspx_dependants;"); + out.popIndent(); + out.printil("}"); + out.println(); + + generateInit(); + generateDestroy(); + } + + /** + * Generates the beginning of the static portion of the servlet. + */ + private void generatePreamble(Node.Nodes page) throws JasperException { + + String servletPackageName = ctxt.getServletPackageName(); + String servletClassName = ctxt.getServletClassName(); + String serviceMethodName = Constants.SERVICE_METHOD_NAME; + + // First the package name: + genPreamblePackage(servletPackageName); + + // Generate imports + genPreambleImports(); + + // Generate class declaration + out.printin("public final class "); + out.print(servletClassName); + out.print(" extends "); + out.println(pageInfo.getExtends()); + out.printin(" implements org.apache.jasper.runtime.JspSourceDependent"); + if (!pageInfo.isThreadSafe()) { + out.println(","); + out.printin(" SingleThreadModel"); + } + out.println(" {"); + out.pushIndent(); + + // Class body begins here + generateDeclarations(page); + + // Static initializations here + genPreambleStaticInitializers(); + + // Class variable declarations + genPreambleClassVariableDeclarations(servletClassName); + + // Constructor + // generateConstructor(className); + + // Methods here + genPreambleMethods(); + + // Now the service method + out.printin("public void "); + out.print(serviceMethodName); + out.println("(HttpServletRequest request, HttpServletResponse response)"); + out.println(" throws java.io.IOException, ServletException {"); + + out.pushIndent(); + out.println(); + + // Local variable declarations + out.printil("PageContext pageContext = null;"); + + if (pageInfo.isSession()) + out.printil("HttpSession session = null;"); + + if (pageInfo.isErrorPage()) { + out.printil("Throwable exception = org.apache.jasper.runtime.JspRuntimeLibrary.getThrowable(request);"); + out.printil("if (exception != null) {"); + out.pushIndent(); + out.printil("response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);"); + out.popIndent(); + out.printil("}"); + } + + out.printil("ServletContext application = null;"); + out.printil("ServletConfig config = null;"); + out.printil("JspWriter out = null;"); + out.printil("Object page = this;"); + + out.printil("JspWriter _jspx_out = null;"); + out.printil("PageContext _jspx_page_context = null;"); + out.println(); + + declareTemporaryScriptingVars(page); + out.println(); + + out.printil("try {"); + out.pushIndent(); + + out.printin("response.setContentType("); + out.print(quote(pageInfo.getContentType())); + out.println(");"); + + if (ctxt.getOptions().isXpoweredBy()) { + out.printil("response.addHeader(\"X-Powered-By\", \"JSP/2.1\");"); + } + + out + .printil("pageContext = _jspxFactory.getPageContext(this, request, response,"); + out.printin("\t\t\t"); + out.print(quote(pageInfo.getErrorPage())); + out.print(", " + pageInfo.isSession()); + out.print(", " + pageInfo.getBuffer()); + out.print(", " + pageInfo.isAutoFlush()); + out.println(");"); + out.printil("_jspx_page_context = pageContext;"); + + out.printil("application = pageContext.getServletContext();"); + out.printil("config = pageContext.getServletConfig();"); + + if (pageInfo.isSession()) + out.printil("session = pageContext.getSession();"); + out.printil("out = pageContext.getOut();"); + out.printil("_jspx_out = out;"); + out.println(); + } + + /** + * Generates an XML Prolog, which includes an XML declaration and an XML + * doctype declaration. + */ + private void generateXmlProlog(Node.Nodes page) { + + /* + * An XML declaration is generated under the following conditions: - + * 'omit-xml-declaration' attribute of action is set to + * "no" or "false" - JSP document without a + */ + String omitXmlDecl = pageInfo.getOmitXmlDecl(); + if ((omitXmlDecl != null && !JspUtil.booleanValue(omitXmlDecl)) + || (omitXmlDecl == null && page.getRoot().isXmlSyntax() + && !pageInfo.hasJspRoot() && !ctxt.isTagFile())) { + String cType = pageInfo.getContentType(); + String charSet = cType.substring(cType.indexOf("charset=") + 8); + out.printil("out.write(\"\\n\");"); + } + + /* + * Output a DOCTYPE declaration if the doctype-root-element appears. If + * doctype-public appears: else + */ + + String doctypeName = pageInfo.getDoctypeName(); + if (doctypeName != null) { + String doctypePublic = pageInfo.getDoctypePublic(); + String doctypeSystem = pageInfo.getDoctypeSystem(); + out.printin("out.write(\"\\n\");"); + } + } + + /* + * Generates the constructor. (shared by servlet and tag handler preamble + * generation) + */ + private void generateConstructor(String className) { + out.printil("public " + className + "() {"); + out.printil("}"); + out.println(); + } + + /** + * A visitor that generates codes for the elements in the page. + */ + class GenerateVisitor extends Node.Visitor { + + /* + * Hashtable containing introspection information on tag handlers: + * : tag prefix : hashtable containing introspection on tag + * handlers: : tag short name : introspection info of tag + * handler for tag + */ + private Hashtable handlerInfos; + + private Hashtable tagVarNumbers; + + private String parent; + + private boolean isSimpleTagParent; // Is parent a SimpleTag? + + private String pushBodyCountVar; + + private String simpleTagHandlerVar; + + private boolean isSimpleTagHandler; + + private boolean isFragment; + + private boolean isTagFile; + + private ServletWriter out; + + private ArrayList methodsBuffered; + + private FragmentHelperClass fragmentHelperClass; + + private int methodNesting; + + private TagInfo tagInfo; + + private ClassLoader loader; + + private int charArrayCount; + + private HashMap textMap; + + /** + * Constructor. + */ + public GenerateVisitor(boolean isTagFile, ServletWriter out, + ArrayList methodsBuffered, + FragmentHelperClass fragmentHelperClass, ClassLoader loader, + TagInfo tagInfo) { + + this.isTagFile = isTagFile; + this.out = out; + this.methodsBuffered = methodsBuffered; + this.fragmentHelperClass = fragmentHelperClass; + this.loader = loader; + this.tagInfo = tagInfo; + methodNesting = 0; + handlerInfos = new Hashtable(); + tagVarNumbers = new Hashtable(); + textMap = new HashMap(); + } + + /** + * Returns an attribute value, optionally URL encoded. If the value is a + * runtime expression, the result is the expression itself, as a string. + * If the result is an EL expression, we insert a call to the + * interpreter. If the result is a Named Attribute we insert the + * generated variable name. Otherwise the result is a string literal, + * quoted and escaped. + * + * @param attr + * An JspAttribute object + * @param encode + * true if to be URL encoded + * @param expectedType + * the expected type for an EL evaluation (ignored for + * attributes that aren't EL expressions) + */ + private String attributeValue(Node.JspAttribute attr, boolean encode, + Class expectedType) { + String v = attr.getValue(); + if (!attr.isNamedAttribute() && (v == null)) + return ""; + + if (attr.isExpression()) { + if (encode) { + return "org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode(String.valueOf(" + + v + "), request.getCharacterEncoding())"; + } + return v; + } else if (attr.isELInterpreterInput()) { + v = attributeValueWithEL(this.isTagFile, v, expectedType, + attr.getEL().getMapName()); + if (encode) { + return "org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode(" + + v + ", request.getCharacterEncoding())"; + } + return v; + } else if (attr.isNamedAttribute()) { + return attr.getNamedAttributeNode().getTemporaryVariableName(); + } else { + if (encode) { + return "org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode(" + + quote(v) + ", request.getCharacterEncoding())"; + } + return quote(v); + } + } + + + /* + * When interpreting the EL attribute value, literals outside the EL + * must not be unescaped but the EL processor will unescape them. + * Therefore, make sure only the EL expressions are processed by the EL + * processor. + */ + private String attributeValueWithEL(boolean isTag, String tx, + Class expectedType, String mapName) { + if (tx==null) return null; + Class type = expectedType; + int size = tx.length(); + StringBuffer output = new StringBuffer(size); + boolean el = false; + int i = 0; + int mark = 0; + char ch; + + while(i < size){ + ch = tx.charAt(i); + + // Start of an EL expression + if (!el && i+1 < size && ch == '$' && tx.charAt(i+1)=='{') { + if (mark < i) { + if (output.length() > 0) { + output.append(" + "); + // Composite expression - must coerce to String + type = String.class; + } + output.append(quote(tx.substring(mark, i))); + } + mark = i; + el = true; + i += 2; + } else if (ch=='\\' && i+1 < size && + (tx.charAt(i+1)=='$' || tx.charAt(i+1)=='}')) { + // Skip an escaped $ or } + i += 2; + } else if (el && ch=='}') { + // End of an EL expression + if (output.length() > 0) { + output.append(" + "); + // Composite expression - must coerce to String + type = String.class; + } + output.append( + JspUtil.interpreterCall(isTag, + tx.substring(mark, i+1), type, + mapName, false)); + mark = i + 1; + el = false; + ++i; + } else { + // Nothing to see here - move to next character + ++i; + } + } + if (!el && mark < i) { + if (output.length() > 0) { + output.append(" + "); + } + output.append(quote(tx.substring(mark, i))); + } + return output.toString(); + } + + + /** + * Prints the attribute value specified in the param action, in the form + * of name=value string. + * + * @param n + * the parent node for the param action nodes. + */ + private void printParams(Node n, String pageParam, boolean literal) + throws JasperException { + + class ParamVisitor extends Node.Visitor { + String separator; + + ParamVisitor(String separator) { + this.separator = separator; + } + + public void visit(Node.ParamAction n) throws JasperException { + + out.print(" + "); + out.print(separator); + out.print(" + "); + out.print("org.apache.jasper.runtime.JspRuntimeLibrary." + + "URLEncode(" + quote(n.getTextAttribute("name")) + + ", request.getCharacterEncoding())"); + out.print("+ \"=\" + "); + out.print(attributeValue(n.getValue(), true, String.class)); + + // The separator is '&' after the second use + separator = "\"&\""; + } + } + + String sep; + if (literal) { + sep = pageParam.indexOf('?') > 0 ? "\"&\"" : "\"?\""; + } else { + sep = "((" + pageParam + ").indexOf('?')>0? '&': '?')"; + } + if (n.getBody() != null) { + n.getBody().visit(new ParamVisitor(sep)); + } + } + + public void visit(Node.Expression n) throws JasperException { + n.setBeginJavaLine(out.getJavaLine()); + out.printin("out.print("); + out.printMultiLn(n.getText()); + out.println(");"); + n.setEndJavaLine(out.getJavaLine()); + } + + public void visit(Node.Scriptlet n) throws JasperException { + n.setBeginJavaLine(out.getJavaLine()); + out.printMultiLn(n.getText()); + out.println(); + n.setEndJavaLine(out.getJavaLine()); + } + + public void visit(Node.ELExpression n) throws JasperException { + n.setBeginJavaLine(out.getJavaLine()); + if (!pageInfo.isELIgnored() && (n.getEL() != null)) { + out.printil("out.write(" + + JspUtil.interpreterCall(this.isTagFile, n.getType() + "{" + + new String(n.getText()) + "}", String.class, + n.getEL().getMapName(), false) + ");"); + } else { + out.printil("out.write(" + + quote(n.getType() + "{" + new String(n.getText()) + "}") + ");"); + } + n.setEndJavaLine(out.getJavaLine()); + } + + public void visit(Node.IncludeAction n) throws JasperException { + + String flush = n.getTextAttribute("flush"); + Node.JspAttribute page = n.getPage(); + + boolean isFlush = false; // default to false; + if ("true".equals(flush)) + isFlush = true; + + n.setBeginJavaLine(out.getJavaLine()); + + String pageParam; + if (page.isNamedAttribute()) { + // If the page for jsp:include was specified via + // jsp:attribute, first generate code to evaluate + // that body. + pageParam = generateNamedAttributeValue(page + .getNamedAttributeNode()); + } else { + pageParam = attributeValue(page, false, String.class); + } + + // If any of the params have their values specified by + // jsp:attribute, prepare those values first. + Node jspBody = findJspBody(n); + if (jspBody != null) { + prepareParams(jspBody); + } else { + prepareParams(n); + } + + out + .printin("org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, " + + pageParam); + printParams(n, pageParam, page.isLiteral()); + out.println(", out, " + isFlush + ");"); + + n.setEndJavaLine(out.getJavaLine()); + } + + /** + * Scans through all child nodes of the given parent for + * subelements. For each element, if its value is specified via + * a Named Attribute (), generate the code to evaluate + * those bodies first. + *

+ * If parent is null, simply returns. + */ + private void prepareParams(Node parent) throws JasperException { + if (parent == null) + return; + + Node.Nodes subelements = parent.getBody(); + if (subelements != null) { + for (int i = 0; i < subelements.size(); i++) { + Node n = subelements.getNode(i); + if (n instanceof Node.ParamAction) { + Node.Nodes paramSubElements = n.getBody(); + for (int j = 0; (paramSubElements != null) + && (j < paramSubElements.size()); j++) { + Node m = paramSubElements.getNode(j); + if (m instanceof Node.NamedAttribute) { + generateNamedAttributeValue((Node.NamedAttribute) m); + } + } + } + } + } + } + + /** + * Finds the subelement of the given parent node. If not + * found, null is returned. + */ + private Node.JspBody findJspBody(Node parent) throws JasperException { + Node.JspBody result = null; + + Node.Nodes subelements = parent.getBody(); + for (int i = 0; (subelements != null) && (i < subelements.size()); i++) { + Node n = subelements.getNode(i); + if (n instanceof Node.JspBody) { + result = (Node.JspBody) n; + break; + } + } + + return result; + } + + public void visit(Node.ForwardAction n) throws JasperException { + Node.JspAttribute page = n.getPage(); + + n.setBeginJavaLine(out.getJavaLine()); + + out.printil("if (true) {"); // So that javac won't complain about + out.pushIndent(); // codes after "return" + + String pageParam; + if (page.isNamedAttribute()) { + // If the page for jsp:forward was specified via + // jsp:attribute, first generate code to evaluate + // that body. + pageParam = generateNamedAttributeValue(page + .getNamedAttributeNode()); + } else { + pageParam = attributeValue(page, false, String.class); + } + + // If any of the params have their values specified by + // jsp:attribute, prepare those values first. + Node jspBody = findJspBody(n); + if (jspBody != null) { + prepareParams(jspBody); + } else { + prepareParams(n); + } + + out.printin("_jspx_page_context.forward("); + out.print(pageParam); + printParams(n, pageParam, page.isLiteral()); + out.println(");"); + if (isTagFile || isFragment) { + out.printil("throw new SkipPageException();"); + } else { + out.printil((methodNesting > 0) ? "return true;" : "return;"); + } + out.popIndent(); + out.printil("}"); + + n.setEndJavaLine(out.getJavaLine()); + // XXX Not sure if we can eliminate dead codes after this. + } + + public void visit(Node.GetProperty n) throws JasperException { + String name = n.getTextAttribute("name"); + String property = n.getTextAttribute("property"); + + n.setBeginJavaLine(out.getJavaLine()); + + if (beanInfo.checkVariable(name)) { + // Bean is defined using useBean, introspect at compile time + Class bean = beanInfo.getBeanType(name); + String beanName = JspUtil.getCanonicalName(bean); + java.lang.reflect.Method meth = JspRuntimeLibrary + .getReadMethod(bean, property); + String methodName = meth.getName(); + out + .printil("out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString(" + + "(((" + + beanName + + ")_jspx_page_context.findAttribute(" + + "\"" + + name + "\"))." + methodName + "())));"); + } else { + // The object could be a custom action with an associated + // VariableInfo entry for this name. + // Get the class name and then introspect at runtime. + out + .printil("out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString" + + "(org.apache.jasper.runtime.JspRuntimeLibrary.handleGetProperty" + + "(_jspx_page_context.getAttribute(\"" + + name + + "\", PageContext.PAGE_SCOPE), \"" + + property + + "\")));"); + } + + n.setEndJavaLine(out.getJavaLine()); + } + + public void visit(Node.SetProperty n) throws JasperException { + String name = n.getTextAttribute("name"); + String property = n.getTextAttribute("property"); + String param = n.getTextAttribute("param"); + Node.JspAttribute value = n.getValue(); + + n.setBeginJavaLine(out.getJavaLine()); + + if ("*".equals(property)) { + out + .printil("org.apache.jasper.runtime.JspRuntimeLibrary.introspect(" + + "_jspx_page_context.findAttribute(" + + "\"" + + name + "\"), request);"); + } else if (value == null) { + if (param == null) + param = property; // default to same as property + out + .printil("org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper(" + + "_jspx_page_context.findAttribute(\"" + + name + + "\"), \"" + + property + + "\", request.getParameter(\"" + + param + + "\"), " + + "request, \"" + + param + + "\", false);"); + } else if (value.isExpression()) { + out + .printil("org.apache.jasper.runtime.JspRuntimeLibrary.handleSetProperty(" + + "_jspx_page_context.findAttribute(\"" + + name + + "\"), \"" + property + "\","); + out.print(attributeValue(value, false, null)); + out.println(");"); + } else if (value.isELInterpreterInput()) { + // We've got to resolve the very call to the interpreter + // at runtime since we don't know what type to expect + // in the general case; we thus can't hard-wire the call + // into the generated code. (XXX We could, however, + // optimize the case where the bean is exposed with + // , much as the code here does for + // getProperty.) + + // The following holds true for the arguments passed to + // JspRuntimeLibrary.handleSetPropertyExpression(): + // - 'pageContext' is a VariableResolver. + // - 'this' (either the generated Servlet or the generated tag + // handler for Tag files) is a FunctionMapper. + out + .printil("org.apache.jasper.runtime.JspRuntimeLibrary.handleSetPropertyExpression(" + + "_jspx_page_context.findAttribute(\"" + + name + + "\"), \"" + + property + + "\", " + + quote(value.getValue()) + + ", " + + "_jspx_page_context, " + + value.getEL().getMapName() + ");"); + } else if (value.isNamedAttribute()) { + // If the value for setProperty was specified via + // jsp:attribute, first generate code to evaluate + // that body. + String valueVarName = generateNamedAttributeValue(value + .getNamedAttributeNode()); + out + .printil("org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper(" + + "_jspx_page_context.findAttribute(\"" + + name + + "\"), \"" + + property + + "\", " + + valueVarName + + ", null, null, false);"); + } else { + out + .printin("org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper(" + + "_jspx_page_context.findAttribute(\"" + + name + + "\"), \"" + property + "\", "); + out.print(attributeValue(value, false, null)); + out.println(", null, null, false);"); + } + + n.setEndJavaLine(out.getJavaLine()); + } + + public void visit(Node.UseBean n) throws JasperException { + + String name = n.getTextAttribute("id"); + String scope = n.getTextAttribute("scope"); + String klass = n.getTextAttribute("class"); + String type = n.getTextAttribute("type"); + Node.JspAttribute beanName = n.getBeanName(); + + // If "class" is specified, try an instantiation at compile time + boolean generateNew = false; + String canonicalName = null; // Canonical name for klass + if (klass != null) { + try { + Class bean = ctxt.getClassLoader().loadClass(klass); + if (klass.indexOf('$') >= 0) { + // Obtain the canonical type name + canonicalName = JspUtil.getCanonicalName(bean); + } else { + canonicalName = klass; + } + int modifiers = bean.getModifiers(); + if (!Modifier.isPublic(modifiers) + || Modifier.isInterface(modifiers) + || Modifier.isAbstract(modifiers)) { + throw new Exception("Invalid bean class modifier"); + } + // Check that there is a 0 arg constructor + bean.getConstructor(new Class[] {}); + // At compile time, we have determined that the bean class + // exists, with a public zero constructor, new() can be + // used for bean instantiation. + generateNew = true; + } catch (Exception e) { + // Cannot instantiate the specified class, either a + // compilation error or a runtime error will be raised, + // depending on a compiler flag. + if (ctxt.getOptions() + .getErrorOnUseBeanInvalidClassAttribute()) { + err.jspError(n, "jsp.error.invalid.bean", klass); + } + if (canonicalName == null) { + // Doing our best here to get a canonical name + // from the binary name, should work 99.99% of time. + canonicalName = klass.replace('$', '.'); + } + } + if (type == null) { + // if type is unspecified, use "class" as type of bean + type = canonicalName; + } + } + + String scopename = "PageContext.PAGE_SCOPE"; // Default to page + String lock = "_jspx_page_context"; + + if ("request".equals(scope)) { + scopename = "PageContext.REQUEST_SCOPE"; + lock = "request"; + } else if ("session".equals(scope)) { + scopename = "PageContext.SESSION_SCOPE"; + lock = "session"; + } else if ("application".equals(scope)) { + scopename = "PageContext.APPLICATION_SCOPE"; + lock = "application"; + } + + n.setBeginJavaLine(out.getJavaLine()); + + // Declare bean + out.printin(type); + out.print(' '); + out.print(name); + out.println(" = null;"); + + // Lock while getting or creating bean + out.printin("synchronized ("); + out.print(lock); + out.println(") {"); + out.pushIndent(); + + // Locate bean from context + out.printin(name); + out.print(" = ("); + out.print(type); + out.print(") _jspx_page_context.getAttribute("); + out.print(quote(name)); + out.print(", "); + out.print(scopename); + out.println(");"); + + // Create bean + /* + * Check if bean is alredy there + */ + out.printin("if ("); + out.print(name); + out.println(" == null){"); + out.pushIndent(); + if (klass == null && beanName == null) { + /* + * If both class name and beanName is not specified, the bean + * must be found locally, otherwise it's an error + */ + out + .printin("throw new java.lang.InstantiationException(\"bean "); + out.print(name); + out.println(" not found within scope\");"); + } else { + /* + * Instantiate the bean if it is not in the specified scope. + */ + if (!generateNew) { + String binaryName; + if (beanName != null) { + if (beanName.isNamedAttribute()) { + // If the value for beanName was specified via + // jsp:attribute, first generate code to evaluate + // that body. + binaryName = generateNamedAttributeValue(beanName + .getNamedAttributeNode()); + } else { + binaryName = attributeValue(beanName, false, + String.class); + } + } else { + // Implies klass is not null + binaryName = quote(klass); + } + out.printil("try {"); + out.pushIndent(); + out.printin(name); + out.print(" = ("); + out.print(type); + out.print(") java.beans.Beans.instantiate("); + out.print("this.getClass().getClassLoader(), "); + out.print(binaryName); + out.println(");"); + out.popIndent(); + /* + * Note: Beans.instantiate throws ClassNotFoundException if + * the bean class is abstract. + */ + out.printil("} catch (ClassNotFoundException exc) {"); + out.pushIndent(); + out + .printil("throw new InstantiationException(exc.getMessage());"); + out.popIndent(); + out.printil("} catch (Exception exc) {"); + out.pushIndent(); + out.printin("throw new ServletException("); + out.print("\"Cannot create bean of class \" + "); + out.print(binaryName); + out.println(", exc);"); + out.popIndent(); + out.printil("}"); // close of try + } else { + // Implies klass is not null + // Generate codes to instantiate the bean class + out.printin(name); + out.print(" = new "); + out.print(canonicalName); + out.println("();"); + } + /* + * Set attribute for bean in the specified scope + */ + out.printin("_jspx_page_context.setAttribute("); + out.print(quote(name)); + out.print(", "); + out.print(name); + out.print(", "); + out.print(scopename); + out.println(");"); + + // Only visit the body when bean is instantiated + visitBody(n); + } + out.popIndent(); + out.printil("}"); + + // End of lock block + out.popIndent(); + out.printil("}"); + + n.setEndJavaLine(out.getJavaLine()); + } + + /** + * @return a string for the form 'attr = "value"' + */ + private String makeAttr(String attr, String value) { + if (value == null) + return ""; + + return " " + attr + "=\"" + value + '\"'; + } + + public void visit(Node.PlugIn n) throws JasperException { + + /** + * A visitor to handle in a plugin + */ + class ParamVisitor extends Node.Visitor { + + private boolean ie; + + ParamVisitor(boolean ie) { + this.ie = ie; + } + + public void visit(Node.ParamAction n) throws JasperException { + + String name = n.getTextAttribute("name"); + if (name.equalsIgnoreCase("object")) + name = "java_object"; + else if (name.equalsIgnoreCase("type")) + name = "java_type"; + + n.setBeginJavaLine(out.getJavaLine()); + // XXX - Fixed a bug here - value used to be output + // inline, which is only okay if value is not an EL + // expression. Also, key/value pairs for the + // embed tag were not being generated correctly. + // Double check that this is now the correct behavior. + if (ie) { + // We want something of the form + // out.println( "" ); + out.printil("out.write( \"\" );"); + out.printil("out.write(\"\\n\");"); + } else { + // We want something of the form + // out.print( " blah=\"" + ... + "\"" ); + out.printil("out.write( \" " + + escape(name) + + "=\\\"\" + " + + attributeValue(n.getValue(), false, + String.class) + " + \"\\\"\" );"); + } + + n.setEndJavaLine(out.getJavaLine()); + } + } + + String type = n.getTextAttribute("type"); + String code = n.getTextAttribute("code"); + String name = n.getTextAttribute("name"); + Node.JspAttribute height = n.getHeight(); + Node.JspAttribute width = n.getWidth(); + String hspace = n.getTextAttribute("hspace"); + String vspace = n.getTextAttribute("vspace"); + String align = n.getTextAttribute("align"); + String iepluginurl = n.getTextAttribute("iepluginurl"); + String nspluginurl = n.getTextAttribute("nspluginurl"); + String codebase = n.getTextAttribute("codebase"); + String archive = n.getTextAttribute("archive"); + String jreversion = n.getTextAttribute("jreversion"); + + String widthStr = null; + if (width != null) { + if (width.isNamedAttribute()) { + widthStr = generateNamedAttributeValue(width + .getNamedAttributeNode()); + } else { + widthStr = attributeValue(width, false, String.class); + } + } + + String heightStr = null; + if (height != null) { + if (height.isNamedAttribute()) { + heightStr = generateNamedAttributeValue(height + .getNamedAttributeNode()); + } else { + heightStr = attributeValue(height, false, String.class); + } + } + + if (iepluginurl == null) + iepluginurl = Constants.IE_PLUGIN_URL; + if (nspluginurl == null) + nspluginurl = Constants.NS_PLUGIN_URL; + + n.setBeginJavaLine(out.getJavaLine()); + + // If any of the params have their values specified by + // jsp:attribute, prepare those values first. + // Look for a params node and prepare its param subelements: + Node.JspBody jspBody = findJspBody(n); + if (jspBody != null) { + Node.Nodes subelements = jspBody.getBody(); + if (subelements != null) { + for (int i = 0; i < subelements.size(); i++) { + Node m = subelements.getNode(i); + if (m instanceof Node.ParamsAction) { + prepareParams(m); + break; + } + } + } + } + + // XXX - Fixed a bug here - width and height can be set + // dynamically. Double-check if this generation is correct. + + // IE style plugin + // + // First compose the runtime output string + String s0 = "'; + + // Then print the output string to the java file + out.printil("out.write(" + quote(s0) + s1 + s2 + " + " + quote(s3) + + ");"); + out.printil("out.write(\"\\n\");"); + + // for java_code + s0 = "'; + out.printil("out.write(" + quote(s0) + ");"); + out.printil("out.write(\"\\n\");"); + + // for java_codebase + if (codebase != null) { + s0 = "'; + out.printil("out.write(" + quote(s0) + ");"); + out.printil("out.write(\"\\n\");"); + } + + // for java_archive + if (archive != null) { + s0 = "'; + out.printil("out.write(" + quote(s0) + ");"); + out.printil("out.write(\"\\n\");"); + } + + // for type + s0 = " for each in the plugin body + */ + if (n.getBody() != null) + n.getBody().visit(new ParamVisitor(true)); + + /* + * Netscape style plugin part + */ + out.printil("out.write(" + quote("") + ");"); + out.printil("out.write(\"\\n\");"); + s0 = " in plugin body + */ + if (n.getBody() != null) + n.getBody().visit(new ParamVisitor(false)); + + out.printil("out.write(" + quote("/>") + ");"); + out.printil("out.write(\"\\n\");"); + + out.printil("out.write(" + quote("") + ");"); + out.printil("out.write(\"\\n\");"); + + /* + * Fallback + */ + if (n.getBody() != null) { + visitBody(n); + out.printil("out.write(\"\\n\");"); + } + + out.printil("out.write(" + quote("") + ");"); + out.printil("out.write(\"\\n\");"); + + out.printil("out.write(" + quote("") + ");"); + out.printil("out.write(\"\\n\");"); + + out.printil("out.write(" + quote("") + ");"); + out.printil("out.write(\"\\n\");"); + + n.setEndJavaLine(out.getJavaLine()); + } + + public void visit(Node.NamedAttribute n) throws JasperException { + // Don't visit body of this tag - we already did earlier. + } + + public void visit(Node.CustomTag n) throws JasperException { + + // Use plugin to generate more efficient code if there is one. + if (n.useTagPlugin()) { + generateTagPlugin(n); + return; + } + + TagHandlerInfo handlerInfo = getTagHandlerInfo(n); + + // Create variable names + String baseVar = createTagVarName(n.getQName(), n.getPrefix(), n + .getLocalName()); + String tagEvalVar = "_jspx_eval_" + baseVar; + String tagHandlerVar = "_jspx_th_" + baseVar; + String tagPushBodyCountVar = "_jspx_push_body_count_" + baseVar; + + // If the tag contains no scripting element, generate its codes + // to a method. + ServletWriter outSave = null; + Node.ChildInfo ci = n.getChildInfo(); + if (ci.isScriptless() && !ci.hasScriptingVars()) { + // The tag handler and its body code can reside in a separate + // method if it is scriptless and does not have any scripting + // variable defined. + + String tagMethod = "_jspx_meth_" + baseVar; + + // Generate a call to this method + out.printin("if ("); + out.print(tagMethod); + out.print("("); + if (parent != null) { + out.print(parent); + out.print(", "); + } + out.print("_jspx_page_context"); + if (pushBodyCountVar != null) { + out.print(", "); + out.print(pushBodyCountVar); + } + out.println("))"); + out.pushIndent(); + out.printil((methodNesting > 0) ? "return true;" : "return;"); + out.popIndent(); + + // Set up new buffer for the method + outSave = out; + /* + * For fragments, their bodies will be generated in fragment + * helper classes, and the Java line adjustments will be done + * there, hence they are set to null here to avoid double + * adjustments. + */ + GenBuffer genBuffer = new GenBuffer(n, + n.implementsSimpleTag() ? null : n.getBody()); + methodsBuffered.add(genBuffer); + out = genBuffer.getOut(); + + methodNesting++; + // Generate code for method declaration + out.println(); + out.pushIndent(); + out.printin("private boolean "); + out.print(tagMethod); + out.print("("); + if (parent != null) { + out.print("javax.servlet.jsp.tagext.JspTag "); + out.print(parent); + out.print(", "); + } + out.print("PageContext _jspx_page_context"); + if (pushBodyCountVar != null) { + out.print(", int[] "); + out.print(pushBodyCountVar); + } + out.println(")"); + out.printil(" throws Throwable {"); + out.pushIndent(); + + // Initilaize local variables used in this method. + if (!isTagFile) { + out + .printil("PageContext pageContext = _jspx_page_context;"); + } + out.printil("JspWriter out = _jspx_page_context.getOut();"); + generateLocalVariables(out, n); + } + + if (n.implementsSimpleTag()) { + generateCustomDoTag(n, handlerInfo, tagHandlerVar); + } else { + /* + * Classic tag handler: Generate code for start element, body, + * and end element + */ + generateCustomStart(n, handlerInfo, tagHandlerVar, tagEvalVar, + tagPushBodyCountVar); + + // visit body + String tmpParent = parent; + parent = tagHandlerVar; + boolean isSimpleTagParentSave = isSimpleTagParent; + isSimpleTagParent = false; + String tmpPushBodyCountVar = null; + if (n.implementsTryCatchFinally()) { + tmpPushBodyCountVar = pushBodyCountVar; + pushBodyCountVar = tagPushBodyCountVar; + } + boolean tmpIsSimpleTagHandler = isSimpleTagHandler; + isSimpleTagHandler = false; + + visitBody(n); + + parent = tmpParent; + isSimpleTagParent = isSimpleTagParentSave; + if (n.implementsTryCatchFinally()) { + pushBodyCountVar = tmpPushBodyCountVar; + } + isSimpleTagHandler = tmpIsSimpleTagHandler; + + generateCustomEnd(n, tagHandlerVar, tagEvalVar, + tagPushBodyCountVar); + } + + if (ci.isScriptless() && !ci.hasScriptingVars()) { + // Generate end of method + if (methodNesting > 0) { + out.printil("return false;"); + } + out.popIndent(); + out.printil("}"); + out.popIndent(); + + methodNesting--; + + // restore previous writer + out = outSave; + } + } + + private static final String SINGLE_QUOTE = "'"; + + private static final String DOUBLE_QUOTE = "\\\""; + + public void visit(Node.UninterpretedTag n) throws JasperException { + + n.setBeginJavaLine(out.getJavaLine()); + + /* + * Write begin tag + */ + out.printin("out.write(\"<"); + out.print(n.getQName()); + + Attributes attrs = n.getNonTaglibXmlnsAttributes(); + int attrsLen = (attrs == null) ? 0 : attrs.getLength(); + for (int i = 0; i < attrsLen; i++) { + out.print(" "); + out.print(attrs.getQName(i)); + out.print("="); + out.print(DOUBLE_QUOTE); + out.print(attrs.getValue(i).replace("\"", """)); + out.print(DOUBLE_QUOTE); + } + + attrs = n.getAttributes(); + attrsLen = (attrs == null) ? 0 : attrs.getLength(); + Node.JspAttribute[] jspAttrs = n.getJspAttributes(); + for (int i = 0; i < attrsLen; i++) { + out.print(" "); + out.print(attrs.getQName(i)); + out.print("="); + if (jspAttrs[i].isELInterpreterInput()) { + out.print("\\\"\" + "); + out.print(attributeValue(jspAttrs[i], false, String.class)); + out.print(" + \"\\\""); + } else { + out.print(DOUBLE_QUOTE); + out.print(attrs.getValue(i).replace("\"", """)); + out.print(DOUBLE_QUOTE); + } + } + + if (n.getBody() != null) { + out.println(">\");"); + + // Visit tag body + visitBody(n); + + /* + * Write end tag + */ + out.printin("out.write(\"\");"); + } else { + out.println("/>\");"); + } + + n.setEndJavaLine(out.getJavaLine()); + } + + public void visit(Node.JspElement n) throws JasperException { + + n.setBeginJavaLine(out.getJavaLine()); + + // Compute attribute value string for XML-style and named + // attributes + Hashtable map = new Hashtable(); + Node.JspAttribute[] attrs = n.getJspAttributes(); + for (int i = 0; attrs != null && i < attrs.length; i++) { + String attrStr = null; + if (attrs[i].isNamedAttribute()) { + attrStr = generateNamedAttributeValue(attrs[i] + .getNamedAttributeNode()); + } else { + attrStr = attributeValue(attrs[i], false, Object.class); + } + String s = " + \" " + attrs[i].getName() + "=\\\"\" + " + + attrStr + " + \"\\\"\""; + map.put(attrs[i].getName(), s); + } + + // Write begin tag, using XML-style 'name' attribute as the + // element name + String elemName = attributeValue(n.getNameAttribute(), false, + String.class); + out.printin("out.write(\"<\""); + out.print(" + " + elemName); + + // Write remaining attributes + Enumeration enumeration = map.keys(); + while (enumeration.hasMoreElements()) { + String attrName = (String) enumeration.nextElement(); + out.print((String) map.get(attrName)); + } + + // Does the have nested tags other than + // + boolean hasBody = false; + Node.Nodes subelements = n.getBody(); + if (subelements != null) { + for (int i = 0; i < subelements.size(); i++) { + Node subelem = subelements.getNode(i); + if (!(subelem instanceof Node.NamedAttribute)) { + hasBody = true; + break; + } + } + } + if (hasBody) { + out.println(" + \">\");"); + + // Smap should not include the body + n.setEndJavaLine(out.getJavaLine()); + + // Visit tag body + visitBody(n); + + // Write end tag + out.printin("out.write(\"\");"); + } else { + out.println(" + \"/>\");"); + n.setEndJavaLine(out.getJavaLine()); + } + } + + public void visit(Node.TemplateText n) throws JasperException { + + String text = n.getText(); + + int textSize = text.length(); + if (textSize == 0) { + return; + } + + if (textSize <= 3) { + // Special case small text strings + n.setBeginJavaLine(out.getJavaLine()); + int lineInc = 0; + for (int i = 0; i < textSize; i++) { + char ch = text.charAt(i); + out.printil("out.write(" + quote(ch) + ");"); + if (i > 0) { + n.addSmap(lineInc); + } + if (ch == '\n') { + lineInc++; + } + } + n.setEndJavaLine(out.getJavaLine()); + return; + } + + if (ctxt.getOptions().genStringAsCharArray()) { + // Generate Strings as char arrays, for performance + ServletWriter caOut; + if (charArrayBuffer == null) { + charArrayBuffer = new GenBuffer(); + caOut = charArrayBuffer.getOut(); + caOut.pushIndent(); + textMap = new HashMap(); + } else { + caOut = charArrayBuffer.getOut(); + } + String charArrayName = (String) textMap.get(text); + if (charArrayName == null) { + charArrayName = "_jspx_char_array_" + charArrayCount++; + textMap.put(text, charArrayName); + caOut.printin("static char[] "); + caOut.print(charArrayName); + caOut.print(" = "); + caOut.print(quote(text)); + caOut.println(".toCharArray();"); + } + + n.setBeginJavaLine(out.getJavaLine()); + out.printil("out.write(" + charArrayName + ");"); + n.setEndJavaLine(out.getJavaLine()); + return; + } + + n.setBeginJavaLine(out.getJavaLine()); + + out.printin(); + StringBuffer sb = new StringBuffer("out.write(\""); + int initLength = sb.length(); + int count = JspUtil.CHUNKSIZE; + int srcLine = 0; // relative to starting srouce line + for (int i = 0; i < text.length(); i++) { + char ch = text.charAt(i); + --count; + switch (ch) { + case '"': + sb.append('\\').append('\"'); + break; + case '\\': + sb.append('\\').append('\\'); + break; + case '\r': + sb.append('\\').append('r'); + break; + case '\n': + sb.append('\\').append('n'); + srcLine++; + + if (breakAtLF || count < 0) { + // Generate an out.write() when see a '\n' in template + sb.append("\");"); + out.println(sb.toString()); + if (i < text.length() - 1) { + out.printin(); + } + sb.setLength(initLength); + count = JspUtil.CHUNKSIZE; + } + // add a Smap for this line + n.addSmap(srcLine); + break; + case '\t': // Not sure we need this + sb.append('\\').append('t'); + break; + default: + sb.append(ch); + } + } + + if (sb.length() > initLength) { + sb.append("\");"); + out.println(sb.toString()); + } + + n.setEndJavaLine(out.getJavaLine()); + } + + public void visit(Node.JspBody n) throws JasperException { + if (n.getBody() != null) { + if (isSimpleTagHandler) { + out.printin(simpleTagHandlerVar); + out.print(".setJspBody("); + generateJspFragment(n, simpleTagHandlerVar); + out.println(");"); + } else { + visitBody(n); + } + } + } + + public void visit(Node.InvokeAction n) throws JasperException { + + n.setBeginJavaLine(out.getJavaLine()); + + // Copy virtual page scope of tag file to page scope of invoking + // page + out.printil("((org.apache.jasper.runtime.JspContextWrapper) this.jspContext).syncBeforeInvoke();"); + String varReaderAttr = n.getTextAttribute("varReader"); + String varAttr = n.getTextAttribute("var"); + if (varReaderAttr != null || varAttr != null) { + out.printil("_jspx_sout = new java.io.StringWriter();"); + } else { + out.printil("_jspx_sout = null;"); + } + + // Invoke fragment, unless fragment is null + out.printin("if ("); + out.print(toGetterMethod(n.getTextAttribute("fragment"))); + out.println(" != null) {"); + out.pushIndent(); + out.printin(toGetterMethod(n.getTextAttribute("fragment"))); + out.println(".invoke(_jspx_sout);"); + out.popIndent(); + out.printil("}"); + + // Store varReader in appropriate scope + if (varReaderAttr != null || varAttr != null) { + String scopeName = n.getTextAttribute("scope"); + out.printin("_jspx_page_context.setAttribute("); + if (varReaderAttr != null) { + out.print(quote(varReaderAttr)); + out.print(", new java.io.StringReader(_jspx_sout.toString())"); + } else { + out.print(quote(varAttr)); + out.print(", _jspx_sout.toString()"); + } + if (scopeName != null) { + out.print(", "); + out.print(getScopeConstant(scopeName)); + } + out.println(");"); + } + + // Restore EL context + out.printil("jspContext.getELContext().putContext(JspContext.class,getJspContext());"); + + n.setEndJavaLine(out.getJavaLine()); + } + + public void visit(Node.DoBodyAction n) throws JasperException { + + n.setBeginJavaLine(out.getJavaLine()); + + // Copy virtual page scope of tag file to page scope of invoking + // page + out.printil("((org.apache.jasper.runtime.JspContextWrapper) this.jspContext).syncBeforeInvoke();"); + + // Invoke body + String varReaderAttr = n.getTextAttribute("varReader"); + String varAttr = n.getTextAttribute("var"); + if (varReaderAttr != null || varAttr != null) { + out.printil("_jspx_sout = new java.io.StringWriter();"); + } else { + out.printil("_jspx_sout = null;"); + } + out.printil("if (getJspBody() != null)"); + out.pushIndent(); + out.printil("getJspBody().invoke(_jspx_sout);"); + out.popIndent(); + + // Store varReader in appropriate scope + if (varReaderAttr != null || varAttr != null) { + String scopeName = n.getTextAttribute("scope"); + out.printin("_jspx_page_context.setAttribute("); + if (varReaderAttr != null) { + out.print(quote(varReaderAttr)); + out + .print(", new java.io.StringReader(_jspx_sout.toString())"); + } else { + out.print(quote(varAttr)); + out.print(", _jspx_sout.toString()"); + } + if (scopeName != null) { + out.print(", "); + out.print(getScopeConstant(scopeName)); + } + out.println(");"); + } + + // Restore EL context + out.printil("jspContext.getELContext().putContext(JspContext.class,getJspContext());"); + + n.setEndJavaLine(out.getJavaLine()); + } + + public void visit(Node.AttributeGenerator n) throws JasperException { + Node.CustomTag tag = n.getTag(); + Node.JspAttribute[] attrs = tag.getJspAttributes(); + for (int i = 0; attrs != null && i < attrs.length; i++) { + if (attrs[i].getName().equals(n.getName())) { + out.print(evaluateAttribute(getTagHandlerInfo(tag), + attrs[i], tag, null)); + break; + } + } + } + + private TagHandlerInfo getTagHandlerInfo(Node.CustomTag n) + throws JasperException { + Hashtable handlerInfosByShortName = (Hashtable) handlerInfos.get(n + .getPrefix()); + if (handlerInfosByShortName == null) { + handlerInfosByShortName = new Hashtable(); + handlerInfos.put(n.getPrefix(), handlerInfosByShortName); + } + TagHandlerInfo handlerInfo = (TagHandlerInfo) handlerInfosByShortName + .get(n.getLocalName()); + if (handlerInfo == null) { + handlerInfo = new TagHandlerInfo(n, n.getTagHandlerClass(), err); + handlerInfosByShortName.put(n.getLocalName(), handlerInfo); + } + return handlerInfo; + } + + private void generateTagPlugin(Node.CustomTag n) throws JasperException { + if (n.getAtSTag() != null) { + n.getAtSTag().visit(this); + } + visitBody(n); + if (n.getAtETag() != null) { + n.getAtETag().visit(this); + } + } + + private void generateCustomStart(Node.CustomTag n, + TagHandlerInfo handlerInfo, String tagHandlerVar, + String tagEvalVar, String tagPushBodyCountVar) + throws JasperException { + + Class tagHandlerClass = handlerInfo.getTagHandlerClass(); + + out.printin("// "); + out.println(n.getQName()); + n.setBeginJavaLine(out.getJavaLine()); + + // Declare AT_BEGIN scripting variables + declareScriptingVars(n, VariableInfo.AT_BEGIN); + saveScriptingVars(n, VariableInfo.AT_BEGIN); + + String tagHandlerClassName = JspUtil + .getCanonicalName(tagHandlerClass); + out.printin(tagHandlerClassName); + out.print(" "); + out.print(tagHandlerVar); + out.print(" = "); + if (isPoolingEnabled && !(n.implementsJspIdConsumer())) { + out.print("("); + out.print(tagHandlerClassName); + out.print(") "); + out.print(n.getTagHandlerPoolName()); + out.print(".get("); + out.print(tagHandlerClassName); + out.println(".class);"); + } else { + out.print("new "); + out.print(tagHandlerClassName); + out.println("();"); + out.printin("org.apache.jasper.runtime.AnnotationHelper.postConstruct("); + out.print(VAR_ANNOTATIONPROCESSOR); + out.print(", "); + out.print(tagHandlerVar); + out.println(");"); + } + + // includes setting the context + generateSetters(n, tagHandlerVar, handlerInfo, false); + + // JspIdConsumer (after context has been set) + if (n.implementsJspIdConsumer()) { + out.printin(tagHandlerVar); + out.print(".setJspId(\""); + out.print(createJspId()); + out.println("\");"); + } + + if (n.implementsTryCatchFinally()) { + out.printin("int[] "); + out.print(tagPushBodyCountVar); + out.println(" = new int[] { 0 };"); + out.printil("try {"); + out.pushIndent(); + } + out.printin("int "); + out.print(tagEvalVar); + out.print(" = "); + out.print(tagHandlerVar); + out.println(".doStartTag();"); + + if (!n.implementsBodyTag()) { + // Synchronize AT_BEGIN scripting variables + syncScriptingVars(n, VariableInfo.AT_BEGIN); + } + + if (!n.hasEmptyBody()) { + out.printin("if ("); + out.print(tagEvalVar); + out.println(" != javax.servlet.jsp.tagext.Tag.SKIP_BODY) {"); + out.pushIndent(); + + // Declare NESTED scripting variables + declareScriptingVars(n, VariableInfo.NESTED); + saveScriptingVars(n, VariableInfo.NESTED); + + if (n.implementsBodyTag()) { + out.printin("if ("); + out.print(tagEvalVar); + out + .println(" != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {"); + // Assume EVAL_BODY_BUFFERED + out.pushIndent(); + out.printil("out = _jspx_page_context.pushBody();"); + if (n.implementsTryCatchFinally()) { + out.printin(tagPushBodyCountVar); + out.println("[0]++;"); + } else if (pushBodyCountVar != null) { + out.printin(pushBodyCountVar); + out.println("[0]++;"); + } + out.printin(tagHandlerVar); + out + .println(".setBodyContent((javax.servlet.jsp.tagext.BodyContent) out);"); + out.printin(tagHandlerVar); + out.println(".doInitBody();"); + + out.popIndent(); + out.printil("}"); + + // Synchronize AT_BEGIN and NESTED scripting variables + syncScriptingVars(n, VariableInfo.AT_BEGIN); + syncScriptingVars(n, VariableInfo.NESTED); + + } else { + // Synchronize NESTED scripting variables + syncScriptingVars(n, VariableInfo.NESTED); + } + + if (n.implementsIterationTag()) { + out.printil("do {"); + out.pushIndent(); + } + } + // Map the Java lines that handles start of custom tags to the + // JSP line for this tag + n.setEndJavaLine(out.getJavaLine()); + } + + private void generateCustomEnd(Node.CustomTag n, String tagHandlerVar, + String tagEvalVar, String tagPushBodyCountVar) { + + if (!n.hasEmptyBody()) { + if (n.implementsIterationTag()) { + out.printin("int evalDoAfterBody = "); + out.print(tagHandlerVar); + out.println(".doAfterBody();"); + + // Synchronize AT_BEGIN and NESTED scripting variables + syncScriptingVars(n, VariableInfo.AT_BEGIN); + syncScriptingVars(n, VariableInfo.NESTED); + + out + .printil("if (evalDoAfterBody != javax.servlet.jsp.tagext.BodyTag.EVAL_BODY_AGAIN)"); + out.pushIndent(); + out.printil("break;"); + out.popIndent(); + + out.popIndent(); + out.printil("} while (true);"); + } + + restoreScriptingVars(n, VariableInfo.NESTED); + + if (n.implementsBodyTag()) { + out.printin("if ("); + out.print(tagEvalVar); + out + .println(" != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {"); + out.pushIndent(); + out.printil("out = _jspx_page_context.popBody();"); + if (n.implementsTryCatchFinally()) { + out.printin(tagPushBodyCountVar); + out.println("[0]--;"); + } else if (pushBodyCountVar != null) { + out.printin(pushBodyCountVar); + out.println("[0]--;"); + } + out.popIndent(); + out.printil("}"); + } + + out.popIndent(); // EVAL_BODY + out.printil("}"); + } + + out.printin("if ("); + out.print(tagHandlerVar); + out + .println(".doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {"); + out.pushIndent(); + if (!n.implementsTryCatchFinally()) { + if (isPoolingEnabled && !(n.implementsJspIdConsumer())) { + out.printin(n.getTagHandlerPoolName()); + out.print(".reuse("); + out.print(tagHandlerVar); + out.println(");"); + } else { + out.printin(tagHandlerVar); + out.println(".release();"); + out.printin("org.apache.jasper.runtime.AnnotationHelper.preDestroy("); + out.print(VAR_ANNOTATIONPROCESSOR); + out.print(", "); + out.print(tagHandlerVar); + out.println(");"); + } + } + if (isTagFile || isFragment) { + out.printil("throw new SkipPageException();"); + } else { + out.printil((methodNesting > 0) ? "return true;" : "return;"); + } + out.popIndent(); + out.printil("}"); + // Synchronize AT_BEGIN scripting variables + syncScriptingVars(n, VariableInfo.AT_BEGIN); + + // TryCatchFinally + if (n.implementsTryCatchFinally()) { + out.popIndent(); // try + out.printil("} catch (Throwable _jspx_exception) {"); + out.pushIndent(); + + out.printin("while ("); + out.print(tagPushBodyCountVar); + out.println("[0]-- > 0)"); + out.pushIndent(); + out.printil("out = _jspx_page_context.popBody();"); + out.popIndent(); + + out.printin(tagHandlerVar); + out.println(".doCatch(_jspx_exception);"); + out.popIndent(); + out.printil("} finally {"); + out.pushIndent(); + out.printin(tagHandlerVar); + out.println(".doFinally();"); + } + + if (isPoolingEnabled && !(n.implementsJspIdConsumer())) { + out.printin(n.getTagHandlerPoolName()); + out.print(".reuse("); + out.print(tagHandlerVar); + out.println(");"); + } else { + out.printin(tagHandlerVar); + out.println(".release();"); + out.printin("org.apache.jasper.runtime.AnnotationHelper.preDestroy("); + out.print(VAR_ANNOTATIONPROCESSOR); + out.print(", "); + out.print(tagHandlerVar); + out.println(");"); + } + + if (n.implementsTryCatchFinally()) { + out.popIndent(); + out.printil("}"); + } + + // Declare and synchronize AT_END scripting variables (must do this + // outside the try/catch/finally block) + declareScriptingVars(n, VariableInfo.AT_END); + syncScriptingVars(n, VariableInfo.AT_END); + + restoreScriptingVars(n, VariableInfo.AT_BEGIN); + } + + private void generateCustomDoTag(Node.CustomTag n, + TagHandlerInfo handlerInfo, String tagHandlerVar) + throws JasperException { + + Class tagHandlerClass = handlerInfo.getTagHandlerClass(); + + n.setBeginJavaLine(out.getJavaLine()); + out.printin("// "); + out.println(n.getQName()); + + // Declare AT_BEGIN scripting variables + declareScriptingVars(n, VariableInfo.AT_BEGIN); + saveScriptingVars(n, VariableInfo.AT_BEGIN); + + String tagHandlerClassName = JspUtil + .getCanonicalName(tagHandlerClass); + out.printin(tagHandlerClassName); + out.print(" "); + out.print(tagHandlerVar); + out.print(" = "); + out.print("new "); + out.print(tagHandlerClassName); + out.println("();"); + + // Resource injection + out.printin("org.apache.jasper.runtime.AnnotationHelper.postConstruct("); + out.print(VAR_ANNOTATIONPROCESSOR); + out.print(", "); + out.print(tagHandlerVar); + out.println(");"); + + generateSetters(n, tagHandlerVar, handlerInfo, true); + + // JspIdConsumer (after context has been set) + if (n.implementsJspIdConsumer()) { + out.printin(tagHandlerVar); + out.print(".setJspId(\""); + out.print(createJspId()); + out.println("\");"); + } + + // Set the body + if (findJspBody(n) == null) { + /* + * Encapsulate body of custom tag invocation in JspFragment and + * pass it to tag handler's setJspBody(), unless tag body is + * empty + */ + if (!n.hasEmptyBody()) { + out.printin(tagHandlerVar); + out.print(".setJspBody("); + generateJspFragment(n, tagHandlerVar); + out.println(");"); + } + } else { + /* + * Body of tag is the body of the element. The visit + * method for that element is going to encapsulate that + * element's body in a JspFragment and pass it to the tag + * handler's setJspBody() + */ + String tmpTagHandlerVar = simpleTagHandlerVar; + simpleTagHandlerVar = tagHandlerVar; + boolean tmpIsSimpleTagHandler = isSimpleTagHandler; + isSimpleTagHandler = true; + visitBody(n); + simpleTagHandlerVar = tmpTagHandlerVar; + isSimpleTagHandler = tmpIsSimpleTagHandler; + } + + out.printin(tagHandlerVar); + out.println(".doTag();"); + + restoreScriptingVars(n, VariableInfo.AT_BEGIN); + + // Synchronize AT_BEGIN scripting variables + syncScriptingVars(n, VariableInfo.AT_BEGIN); + + // Declare and synchronize AT_END scripting variables + declareScriptingVars(n, VariableInfo.AT_END); + syncScriptingVars(n, VariableInfo.AT_END); + + // Resource injection + out.printin("org.apache.jasper.runtime.AnnotationHelper.preDestroy("); + out.print(VAR_ANNOTATIONPROCESSOR); + out.print(", "); + out.print(tagHandlerVar); + out.println(");"); + + n.setEndJavaLine(out.getJavaLine()); + } + + private void declareScriptingVars(Node.CustomTag n, int scope) { + + Vector vec = n.getScriptingVars(scope); + if (vec != null) { + for (int i = 0; i < vec.size(); i++) { + Object elem = vec.elementAt(i); + if (elem instanceof VariableInfo) { + VariableInfo varInfo = (VariableInfo) elem; + if (varInfo.getDeclare()) { + out.printin(varInfo.getClassName()); + out.print(" "); + out.print(varInfo.getVarName()); + out.println(" = null;"); + } + } else { + TagVariableInfo tagVarInfo = (TagVariableInfo) elem; + if (tagVarInfo.getDeclare()) { + String varName = tagVarInfo.getNameGiven(); + if (varName == null) { + varName = n.getTagData().getAttributeString( + tagVarInfo.getNameFromAttribute()); + } else if (tagVarInfo.getNameFromAttribute() != null) { + // alias + continue; + } + out.printin(tagVarInfo.getClassName()); + out.print(" "); + out.print(varName); + out.println(" = null;"); + } + } + } + } + } + + /* + * This method is called as part of the custom tag's start element. + * + * If the given custom tag has a custom nesting level greater than 0, + * save the current values of its scripting variables to temporary + * variables, so those values may be restored in the tag's end element. + * This way, the scripting variables may be synchronized by the given + * tag without affecting their original values. + */ + private void saveScriptingVars(Node.CustomTag n, int scope) { + if (n.getCustomNestingLevel() == 0) { + return; + } + + TagVariableInfo[] tagVarInfos = n.getTagVariableInfos(); + VariableInfo[] varInfos = n.getVariableInfos(); + if ((varInfos.length == 0) && (tagVarInfos.length == 0)) { + return; + } + + if (varInfos.length > 0) { + for (int i = 0; i < varInfos.length; i++) { + if (varInfos[i].getScope() != scope) + continue; + // If the scripting variable has been declared, skip codes + // for saving and restoring it. + if (n.getScriptingVars(scope).contains(varInfos[i])) + continue; + String varName = varInfos[i].getVarName(); + String tmpVarName = "_jspx_" + varName + "_" + + n.getCustomNestingLevel(); + out.printin(tmpVarName); + out.print(" = "); + out.print(varName); + out.println(";"); + } + } else { + for (int i = 0; i < tagVarInfos.length; i++) { + if (tagVarInfos[i].getScope() != scope) + continue; + // If the scripting variable has been declared, skip codes + // for saving and restoring it. + if (n.getScriptingVars(scope).contains(tagVarInfos[i])) + continue; + String varName = tagVarInfos[i].getNameGiven(); + if (varName == null) { + varName = n.getTagData().getAttributeString( + tagVarInfos[i].getNameFromAttribute()); + } else if (tagVarInfos[i].getNameFromAttribute() != null) { + // alias + continue; + } + String tmpVarName = "_jspx_" + varName + "_" + + n.getCustomNestingLevel(); + out.printin(tmpVarName); + out.print(" = "); + out.print(varName); + out.println(";"); + } + } + } + + /* + * This method is called as part of the custom tag's end element. + * + * If the given custom tag has a custom nesting level greater than 0, + * restore its scripting variables to their original values that were + * saved in the tag's start element. + */ + private void restoreScriptingVars(Node.CustomTag n, int scope) { + if (n.getCustomNestingLevel() == 0) { + return; + } + + TagVariableInfo[] tagVarInfos = n.getTagVariableInfos(); + VariableInfo[] varInfos = n.getVariableInfos(); + if ((varInfos.length == 0) && (tagVarInfos.length == 0)) { + return; + } + + if (varInfos.length > 0) { + for (int i = 0; i < varInfos.length; i++) { + if (varInfos[i].getScope() != scope) + continue; + // If the scripting variable has been declared, skip codes + // for saving and restoring it. + if (n.getScriptingVars(scope).contains(varInfos[i])) + continue; + String varName = varInfos[i].getVarName(); + String tmpVarName = "_jspx_" + varName + "_" + + n.getCustomNestingLevel(); + out.printin(varName); + out.print(" = "); + out.print(tmpVarName); + out.println(";"); + } + } else { + for (int i = 0; i < tagVarInfos.length; i++) { + if (tagVarInfos[i].getScope() != scope) + continue; + // If the scripting variable has been declared, skip codes + // for saving and restoring it. + if (n.getScriptingVars(scope).contains(tagVarInfos[i])) + continue; + String varName = tagVarInfos[i].getNameGiven(); + if (varName == null) { + varName = n.getTagData().getAttributeString( + tagVarInfos[i].getNameFromAttribute()); + } else if (tagVarInfos[i].getNameFromAttribute() != null) { + // alias + continue; + } + String tmpVarName = "_jspx_" + varName + "_" + + n.getCustomNestingLevel(); + out.printin(varName); + out.print(" = "); + out.print(tmpVarName); + out.println(";"); + } + } + } + + /* + * Synchronizes the scripting variables of the given custom tag for the + * given scope. + */ + private void syncScriptingVars(Node.CustomTag n, int scope) { + TagVariableInfo[] tagVarInfos = n.getTagVariableInfos(); + VariableInfo[] varInfos = n.getVariableInfos(); + + if ((varInfos.length == 0) && (tagVarInfos.length == 0)) { + return; + } + + if (varInfos.length > 0) { + for (int i = 0; i < varInfos.length; i++) { + if (varInfos[i].getScope() == scope) { + out.printin(varInfos[i].getVarName()); + out.print(" = ("); + out.print(varInfos[i].getClassName()); + out.print(") _jspx_page_context.findAttribute("); + out.print(quote(varInfos[i].getVarName())); + out.println(");"); + } + } + } else { + for (int i = 0; i < tagVarInfos.length; i++) { + if (tagVarInfos[i].getScope() == scope) { + String name = tagVarInfos[i].getNameGiven(); + if (name == null) { + name = n.getTagData().getAttributeString( + tagVarInfos[i].getNameFromAttribute()); + } else if (tagVarInfos[i].getNameFromAttribute() != null) { + // alias + continue; + } + out.printin(name); + out.print(" = ("); + out.print(tagVarInfos[i].getClassName()); + out.print(") _jspx_page_context.findAttribute("); + out.print(quote(name)); + out.println(");"); + } + } + } + } + + private String getJspContextVar() { + if (this.isTagFile) { + return "this.getJspContext()"; + } else { + return "_jspx_page_context"; + } + } + + private String getExpressionFactoryVar() { + return VAR_EXPRESSIONFACTORY; + } + + /* + * Creates a tag variable name by concatenating the given prefix and + * shortName and endcoded to make the resultant string a valid Java + * Identifier. + */ + private String createTagVarName(String fullName, String prefix, + String shortName) { + + String varName; + synchronized (tagVarNumbers) { + varName = prefix + "_" + shortName + "_"; + if (tagVarNumbers.get(fullName) != null) { + Integer i = (Integer) tagVarNumbers.get(fullName); + varName = varName + i.intValue(); + tagVarNumbers.put(fullName, new Integer(i.intValue() + 1)); + } else { + tagVarNumbers.put(fullName, new Integer(1)); + varName = varName + "0"; + } + } + return JspUtil.makeJavaIdentifier(varName); + } + + private String evaluateAttribute(TagHandlerInfo handlerInfo, + Node.JspAttribute attr, Node.CustomTag n, String tagHandlerVar) + throws JasperException { + + String attrValue = attr.getValue(); + if (attrValue == null) { + if (attr.isNamedAttribute()) { + if (n.checkIfAttributeIsJspFragment(attr.getName())) { + // XXX - no need to generate temporary variable here + attrValue = generateNamedAttributeJspFragment(attr + .getNamedAttributeNode(), tagHandlerVar); + } else { + attrValue = generateNamedAttributeValue(attr + .getNamedAttributeNode()); + } + } else { + return null; + } + } + + String localName = attr.getLocalName(); + + Method m = null; + Class[] c = null; + if (attr.isDynamic()) { + c = OBJECT_CLASS; + } else { + m = handlerInfo.getSetterMethod(localName); + if (m == null) { + err.jspError(n, "jsp.error.unable.to_find_method", attr + .getName()); + } + c = m.getParameterTypes(); + // XXX assert(c.length > 0) + } + + if (attr.isExpression()) { + // Do nothing + } else if (attr.isNamedAttribute()) { + if (!n.checkIfAttributeIsJspFragment(attr.getName()) + && !attr.isDynamic()) { + attrValue = convertString(c[0], attrValue, localName, + handlerInfo.getPropertyEditorClass(localName), true); + } + } else if (attr.isELInterpreterInput()) { + + // results buffer + StringBuffer sb = new StringBuffer(64); + + TagAttributeInfo tai = attr.getTagAttributeInfo(); + + // generate elContext reference + sb.append(getJspContextVar()); + sb.append(".getELContext()"); + String elContext = sb.toString(); + if (attr.getEL() != null && attr.getEL().getMapName() != null) { + sb.setLength(0); + sb.append("new org.apache.jasper.el.ELContextWrapper("); + sb.append(elContext); + sb.append(','); + sb.append(attr.getEL().getMapName()); + sb.append(')'); + elContext = sb.toString(); + } + + // reset buffer + sb.setLength(0); + + // create our mark + sb.append(n.getStart().toString()); + sb.append(" '"); + sb.append(attrValue); + sb.append('\''); + String mark = sb.toString(); + + // reset buffer + sb.setLength(0); + + // depending on type + if (attr.isDeferredInput() + || ((tai != null) && ValueExpression.class.getName().equals(tai.getTypeName()))) { + sb.append("new org.apache.jasper.el.JspValueExpression("); + sb.append(quote(mark)); + sb.append(','); + sb.append(getExpressionFactoryVar()); + sb.append(".createValueExpression("); + if (attr.getEL() != null) { // optimize + sb.append(elContext); + sb.append(','); + } + sb.append(quote(attrValue)); + sb.append(','); + sb.append(JspUtil.toJavaSourceTypeFromTld(attr.getExpectedTypeName())); + sb.append("))"); + // should the expression be evaluated before passing to + // the setter? + boolean evaluate = false; + if (tai.canBeRequestTime()) { + evaluate = true; // JSP.2.3.2 + } + if (attr.isDeferredInput()) { + evaluate = false; // JSP.2.3.3 + } + if (attr.isDeferredInput() && tai.canBeRequestTime()) { + evaluate = !attrValue.contains("#{"); // JSP.2.3.5 + } + if (evaluate) { + sb.append(".getValue("); + sb.append(getJspContextVar()); + sb.append(".getELContext()"); + sb.append(")"); + } + attrValue = sb.toString(); + } else if (attr.isDeferredMethodInput() + || ((tai != null) && MethodExpression.class.getName().equals(tai.getTypeName()))) { + sb.append("new org.apache.jasper.el.JspMethodExpression("); + sb.append(quote(mark)); + sb.append(','); + sb.append(getExpressionFactoryVar()); + sb.append(".createMethodExpression("); + sb.append(elContext); + sb.append(','); + sb.append(quote(attrValue)); + sb.append(','); + sb.append(JspUtil.toJavaSourceTypeFromTld(attr.getExpectedTypeName())); + sb.append(','); + sb.append("new Class[] {"); + + String[] p = attr.getParameterTypeNames(); + for (int i = 0; i < p.length; i++) { + sb.append(JspUtil.toJavaSourceTypeFromTld(p[i])); + sb.append(','); + } + if (p.length > 0) { + sb.setLength(sb.length() - 1); + } + + sb.append("}))"); + attrValue = sb.toString(); + } else { + // run attrValue through the expression interpreter + String mapName = (attr.getEL() != null) ? attr.getEL() + .getMapName() : null; + attrValue = attributeValueWithEL(this.isTagFile, + attrValue, c[0], mapName); + } + } else { + attrValue = convertString(c[0], attrValue, localName, + handlerInfo.getPropertyEditorClass(localName), false); + } + return attrValue; + } + + /** + * Generate code to create a map for the alias variables + * + * @return the name of the map + */ + private String generateAliasMap(Node.CustomTag n, String tagHandlerVar) + throws JasperException { + + TagVariableInfo[] tagVars = n.getTagVariableInfos(); + String aliasMapVar = null; + + boolean aliasSeen = false; + for (int i = 0; i < tagVars.length; i++) { + + String nameFrom = tagVars[i].getNameFromAttribute(); + if (nameFrom != null) { + String aliasedName = n.getAttributeValue(nameFrom); + if (aliasedName == null) + continue; + + if (!aliasSeen) { + out.printin("java.util.HashMap "); + aliasMapVar = tagHandlerVar + "_aliasMap"; + out.print(aliasMapVar); + out.println(" = new java.util.HashMap();"); + aliasSeen = true; + } + out.printin(aliasMapVar); + out.print(".put("); + out.print(quote(tagVars[i].getNameGiven())); + out.print(", "); + out.print(quote(aliasedName)); + out.println(");"); + } + } + return aliasMapVar; + } + + private void generateSetters(Node.CustomTag n, String tagHandlerVar, + TagHandlerInfo handlerInfo, boolean simpleTag) + throws JasperException { + + // Set context + if (simpleTag) { + // Generate alias map + String aliasMapVar = null; + if (n.isTagFile()) { + aliasMapVar = generateAliasMap(n, tagHandlerVar); + } + out.printin(tagHandlerVar); + if (aliasMapVar == null) { + out.println(".setJspContext(_jspx_page_context);"); + } else { + out.print(".setJspContext(_jspx_page_context, "); + out.print(aliasMapVar); + out.println(");"); + } + } else { + out.printin(tagHandlerVar); + out.println(".setPageContext(_jspx_page_context);"); + } + + // Set parent + if (isTagFile && parent == null) { + out.printin(tagHandlerVar); + out.print(".setParent("); + out.print("new javax.servlet.jsp.tagext.TagAdapter("); + out.print("(javax.servlet.jsp.tagext.SimpleTag) this ));"); + } else if (!simpleTag) { + out.printin(tagHandlerVar); + out.print(".setParent("); + if (parent != null) { + if (isSimpleTagParent) { + out.print("new javax.servlet.jsp.tagext.TagAdapter("); + out.print("(javax.servlet.jsp.tagext.SimpleTag) "); + out.print(parent); + out.println("));"); + } else { + out.print("(javax.servlet.jsp.tagext.Tag) "); + out.print(parent); + out.println(");"); + } + } else { + out.println("null);"); + } + } else { + // The setParent() method need not be called if the value being + // passed is null, since SimpleTag instances are not reused + if (parent != null) { + out.printin(tagHandlerVar); + out.print(".setParent("); + out.print(parent); + out.println(");"); + } + } + + // need to handle deferred values and methods + Node.JspAttribute[] attrs = n.getJspAttributes(); + for (int i = 0; attrs != null && i < attrs.length; i++) { + String attrValue = evaluateAttribute(handlerInfo, attrs[i], n, + tagHandlerVar); + + Mark m = n.getStart(); + out.printil("// "+m.getFile()+"("+m.getLineNumber()+","+m.getColumnNumber()+") "+ attrs[i].getTagAttributeInfo()); + if (attrs[i].isDynamic()) { + out.printin(tagHandlerVar); + out.print("."); + out.print("setDynamicAttribute("); + String uri = attrs[i].getURI(); + if ("".equals(uri) || (uri == null)) { + out.print("null"); + } else { + out.print("\"" + attrs[i].getURI() + "\""); + } + out.print(", \""); + out.print(attrs[i].getLocalName()); + out.print("\", "); + out.print(attrValue); + out.println(");"); + } else { + out.printin(tagHandlerVar); + out.print("."); + out.print(handlerInfo.getSetterMethod( + attrs[i].getLocalName()).getName()); + out.print("("); + out.print(attrValue); + out.println(");"); + } + } + } + + /* + * @param c The target class to which to coerce the given string @param + * s The string value @param attrName The name of the attribute whose + * value is being supplied @param propEditorClass The property editor + * for the given attribute @param isNamedAttribute true if the given + * attribute is a named attribute (that is, specified using the + * jsp:attribute standard action), and false otherwise + */ + private String convertString(Class c, String s, String attrName, + Class propEditorClass, boolean isNamedAttribute) + throws JasperException { + + String quoted = s; + if (!isNamedAttribute) { + quoted = quote(s); + } + + if (propEditorClass != null) { + String className = JspUtil.getCanonicalName(c); + return "(" + + className + + ")org.apache.jasper.runtime.JspRuntimeLibrary.getValueFromBeanInfoPropertyEditor(" + + className + ".class, \"" + attrName + "\", " + quoted + + ", " + JspUtil.getCanonicalName(propEditorClass) + + ".class)"; + } else if (c == String.class) { + return quoted; + } else if (c == boolean.class) { + return JspUtil.coerceToPrimitiveBoolean(s, isNamedAttribute); + } else if (c == Boolean.class) { + return JspUtil.coerceToBoolean(s, isNamedAttribute); + } else if (c == byte.class) { + return JspUtil.coerceToPrimitiveByte(s, isNamedAttribute); + } else if (c == Byte.class) { + return JspUtil.coerceToByte(s, isNamedAttribute); + } else if (c == char.class) { + return JspUtil.coerceToChar(s, isNamedAttribute); + } else if (c == Character.class) { + return JspUtil.coerceToCharacter(s, isNamedAttribute); + } else if (c == double.class) { + return JspUtil.coerceToPrimitiveDouble(s, isNamedAttribute); + } else if (c == Double.class) { + return JspUtil.coerceToDouble(s, isNamedAttribute); + } else if (c == float.class) { + return JspUtil.coerceToPrimitiveFloat(s, isNamedAttribute); + } else if (c == Float.class) { + return JspUtil.coerceToFloat(s, isNamedAttribute); + } else if (c == int.class) { + return JspUtil.coerceToInt(s, isNamedAttribute); + } else if (c == Integer.class) { + return JspUtil.coerceToInteger(s, isNamedAttribute); + } else if (c == short.class) { + return JspUtil.coerceToPrimitiveShort(s, isNamedAttribute); + } else if (c == Short.class) { + return JspUtil.coerceToShort(s, isNamedAttribute); + } else if (c == long.class) { + return JspUtil.coerceToPrimitiveLong(s, isNamedAttribute); + } else if (c == Long.class) { + return JspUtil.coerceToLong(s, isNamedAttribute); + } else if (c == Object.class) { + return "new String(" + quoted + ")"; + } else { + String className = JspUtil.getCanonicalName(c); + return "(" + + className + + ")org.apache.jasper.runtime.JspRuntimeLibrary.getValueFromPropertyEditorManager(" + + className + ".class, \"" + attrName + "\", " + quoted + + ")"; + } + } + + /* + * Converts the scope string representation, whose possible values are + * "page", "request", "session", and "application", to the corresponding + * scope constant. + */ + private String getScopeConstant(String scope) { + String scopeName = "PageContext.PAGE_SCOPE"; // Default to page + + if ("request".equals(scope)) { + scopeName = "PageContext.REQUEST_SCOPE"; + } else if ("session".equals(scope)) { + scopeName = "PageContext.SESSION_SCOPE"; + } else if ("application".equals(scope)) { + scopeName = "PageContext.APPLICATION_SCOPE"; + } + + return scopeName; + } + + /** + * Generates anonymous JspFragment inner class which is passed as an + * argument to SimpleTag.setJspBody(). + */ + private void generateJspFragment(Node n, String tagHandlerVar) + throws JasperException { + // XXX - A possible optimization here would be to check to see + // if the only child of the parent node is TemplateText. If so, + // we know there won't be any parameters, etc, so we can + // generate a low-overhead JspFragment that just echoes its + // body. The implementation of this fragment can come from + // the org.apache.jasper.runtime package as a support class. + FragmentHelperClass.Fragment fragment = fragmentHelperClass + .openFragment(n, tagHandlerVar, methodNesting); + ServletWriter outSave = out; + out = fragment.getGenBuffer().getOut(); + String tmpParent = parent; + parent = "_jspx_parent"; + boolean isSimpleTagParentSave = isSimpleTagParent; + isSimpleTagParent = true; + boolean tmpIsFragment = isFragment; + isFragment = true; + String pushBodyCountVarSave = pushBodyCountVar; + if (pushBodyCountVar != null) { + // Use a fixed name for push body count, to simplify code gen + pushBodyCountVar = "_jspx_push_body_count"; + } + visitBody(n); + out = outSave; + parent = tmpParent; + isSimpleTagParent = isSimpleTagParentSave; + isFragment = tmpIsFragment; + pushBodyCountVar = pushBodyCountVarSave; + fragmentHelperClass.closeFragment(fragment, methodNesting); + // XXX - Need to change pageContext to jspContext if + // we're not in a place where pageContext is defined (e.g. + // in a fragment or in a tag file. + out.print("new " + fragmentHelperClass.getClassName() + "( " + + fragment.getId() + ", _jspx_page_context, " + + tagHandlerVar + ", " + pushBodyCountVar + ")"); + } + + /** + * Generate the code required to obtain the runtime value of the given + * named attribute. + * + * @return The name of the temporary variable the result is stored in. + */ + public String generateNamedAttributeValue(Node.NamedAttribute n) + throws JasperException { + + String varName = n.getTemporaryVariableName(); + + // If the only body element for this named attribute node is + // template text, we need not generate an extra call to + // pushBody and popBody. Maybe we can further optimize + // here by getting rid of the temporary variable, but in + // reality it looks like javac does this for us. + Node.Nodes body = n.getBody(); + if (body != null) { + boolean templateTextOptimization = false; + if (body.size() == 1) { + Node bodyElement = body.getNode(0); + if (bodyElement instanceof Node.TemplateText) { + templateTextOptimization = true; + out.printil("String " + + varName + + " = " + + quote(new String( + ((Node.TemplateText) bodyElement) + .getText())) + ";"); + } + } + + // XXX - Another possible optimization would be for + // lone EL expressions (no need to pushBody here either). + + if (!templateTextOptimization) { + out.printil("out = _jspx_page_context.pushBody();"); + visitBody(n); + out.printil("String " + varName + " = " + + "((javax.servlet.jsp.tagext.BodyContent)" + + "out).getString();"); + out.printil("out = _jspx_page_context.popBody();"); + } + } else { + // Empty body must be treated as "" + out.printil("String " + varName + " = \"\";"); + } + + return varName; + } + + /** + * Similar to generateNamedAttributeValue, but create a JspFragment + * instead. + * + * @param n + * The parent node of the named attribute + * @param tagHandlerVar + * The variable the tag handler is stored in, so the fragment + * knows its parent tag. + * @return The name of the temporary variable the fragment is stored in. + */ + public String generateNamedAttributeJspFragment(Node.NamedAttribute n, + String tagHandlerVar) throws JasperException { + String varName = n.getTemporaryVariableName(); + + out.printin("javax.servlet.jsp.tagext.JspFragment " + varName + + " = "); + generateJspFragment(n, tagHandlerVar); + out.println(";"); + + return varName; + } + } + + private static void generateLocalVariables(ServletWriter out, Node n) + throws JasperException { + Node.ChildInfo ci; + if (n instanceof Node.CustomTag) { + ci = ((Node.CustomTag) n).getChildInfo(); + } else if (n instanceof Node.JspBody) { + ci = ((Node.JspBody) n).getChildInfo(); + } else if (n instanceof Node.NamedAttribute) { + ci = ((Node.NamedAttribute) n).getChildInfo(); + } else { + // Cannot access err since this method is static, but at + // least flag an error. + throw new JasperException("Unexpected Node Type"); + // err.getString( + // "jsp.error.internal.unexpected_node_type" ) ); + } + + if (ci.hasUseBean()) { + out + .printil("HttpSession session = _jspx_page_context.getSession();"); + out + .printil("ServletContext application = _jspx_page_context.getServletContext();"); + } + if (ci.hasUseBean() || ci.hasIncludeAction() || ci.hasSetProperty() + || ci.hasParamAction()) { + out + .printil("HttpServletRequest request = (HttpServletRequest)_jspx_page_context.getRequest();"); + } + if (ci.hasIncludeAction()) { + out + .printil("HttpServletResponse response = (HttpServletResponse)_jspx_page_context.getResponse();"); + } + } + + /** + * Common part of postamble, shared by both servlets and tag files. + */ + private void genCommonPostamble() { + // Append any methods that were generated in the buffer. + for (int i = 0; i < methodsBuffered.size(); i++) { + GenBuffer methodBuffer = (GenBuffer) methodsBuffered.get(i); + methodBuffer.adjustJavaLines(out.getJavaLine() - 1); + out.printMultiLn(methodBuffer.toString()); + } + + // Append the helper class + if (fragmentHelperClass.isUsed()) { + fragmentHelperClass.generatePostamble(); + fragmentHelperClass.adjustJavaLines(out.getJavaLine() - 1); + out.printMultiLn(fragmentHelperClass.toString()); + } + + // Append char array declarations + if (charArrayBuffer != null) { + out.printMultiLn(charArrayBuffer.toString()); + } + + // Close the class definition + out.popIndent(); + out.printil("}"); + } + + /** + * Generates the ending part of the static portion of the servlet. + */ + private void generatePostamble(Node.Nodes page) { + out.popIndent(); + out.printil("} catch (Throwable t) {"); + out.pushIndent(); + out.printil("if (!(t instanceof SkipPageException)){"); + out.pushIndent(); + out.printil("out = _jspx_out;"); + out.printil("if (out != null && out.getBufferSize() != 0)"); + out.pushIndent(); + out.printil("try { out.clearBuffer(); } catch (java.io.IOException e) {}"); + out.popIndent(); + + out + .printil("if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);"); + out.popIndent(); + out.printil("}"); + out.popIndent(); + out.printil("} finally {"); + out.pushIndent(); + + out + .printil("_jspxFactory.releasePageContext(_jspx_page_context);"); + + out.popIndent(); + out.printil("}"); + + // Close the service method + out.popIndent(); + out.printil("}"); + + // Generated methods, helper classes, etc. + genCommonPostamble(); + } + + /** + * Constructor. + */ + Generator(ServletWriter out, Compiler compiler) { + this.out = out; + methodsBuffered = new ArrayList(); + charArrayBuffer = null; + err = compiler.getErrorDispatcher(); + ctxt = compiler.getCompilationContext(); + fragmentHelperClass = new FragmentHelperClass("Helper"); + pageInfo = compiler.getPageInfo(); + + /* + * Temporary hack. If a JSP page uses the "extends" attribute of the + * page directive, the _jspInit() method of the generated servlet class + * will not be called (it is only called for those generated servlets + * that extend HttpJspBase, the default), causing the tag handler pools + * not to be initialized and resulting in a NPE. The JSP spec needs to + * clarify whether containers can override init() and destroy(). For + * now, we just disable tag pooling for pages that use "extends". + */ + if (pageInfo.getExtends(false) == null) { + isPoolingEnabled = ctxt.getOptions().isPoolingEnabled(); + } else { + isPoolingEnabled = false; + } + beanInfo = pageInfo.getBeanRepository(); + breakAtLF = ctxt.getOptions().getMappedFile(); + if (isPoolingEnabled) { + tagHandlerPoolNames = new Vector(); + } + } + + /** + * The main entry for Generator. + * + * @param out + * The servlet output writer + * @param compiler + * The compiler + * @param page + * The input page + */ + public static void generate(ServletWriter out, Compiler compiler, + Node.Nodes page) throws JasperException { + + Generator gen = new Generator(out, compiler); + + if (gen.isPoolingEnabled) { + gen.compileTagHandlerPoolList(page); + } + if (gen.ctxt.isTagFile()) { + JasperTagInfo tagInfo = (JasperTagInfo) gen.ctxt.getTagInfo(); + gen.generateTagHandlerPreamble(tagInfo, page); + + if (gen.ctxt.isPrototypeMode()) { + return; + } + + gen.generateXmlProlog(page); + gen.fragmentHelperClass.generatePreamble(); + page.visit(gen.new GenerateVisitor(gen.ctxt.isTagFile(), out, + gen.methodsBuffered, gen.fragmentHelperClass, gen.ctxt + .getClassLoader(), tagInfo)); + gen.generateTagHandlerPostamble(tagInfo); + } else { + gen.generatePreamble(page); + gen.generateXmlProlog(page); + gen.fragmentHelperClass.generatePreamble(); + page.visit(gen.new GenerateVisitor(gen.ctxt.isTagFile(), out, + gen.methodsBuffered, gen.fragmentHelperClass, gen.ctxt + .getClassLoader(), null)); + gen.generatePostamble(page); + } + } + + /* + * Generates tag handler preamble. + */ + private void generateTagHandlerPreamble(JasperTagInfo tagInfo, + Node.Nodes tag) throws JasperException { + + // Generate package declaration + String className = tagInfo.getTagClassName(); + int lastIndex = className.lastIndexOf('.'); + if (lastIndex != -1) { + String pkgName = className.substring(0, lastIndex); + genPreamblePackage(pkgName); + className = className.substring(lastIndex + 1); + } + + // Generate imports + genPreambleImports(); + + // Generate class declaration + out.printin("public final class "); + out.println(className); + out.printil(" extends javax.servlet.jsp.tagext.SimpleTagSupport"); + out.printin(" implements org.apache.jasper.runtime.JspSourceDependent"); + if (tagInfo.hasDynamicAttributes()) { + out.println(","); + out.printin(" javax.servlet.jsp.tagext.DynamicAttributes"); + } + out.println(" {"); + out.println(); + out.pushIndent(); + + /* + * Class body begins here + */ + generateDeclarations(tag); + + // Static initializations here + genPreambleStaticInitializers(); + + out.printil("private JspContext jspContext;"); + + // Declare writer used for storing result of fragment/body invocation + // if 'varReader' or 'var' attribute is specified + out.printil("private java.io.Writer _jspx_sout;"); + + // Class variable declarations + genPreambleClassVariableDeclarations(tagInfo.getTagName()); + + generateSetJspContext(tagInfo); + + // Tag-handler specific declarations + generateTagHandlerAttributes(tagInfo); + if (tagInfo.hasDynamicAttributes()) + generateSetDynamicAttribute(); + + // Methods here + genPreambleMethods(); + + // Now the doTag() method + out.printil("public void doTag() throws JspException, java.io.IOException {"); + + if (ctxt.isPrototypeMode()) { + out.printil("}"); + out.popIndent(); + out.printil("}"); + return; + } + + out.pushIndent(); + + /* + * According to the spec, 'pageContext' must not be made available as an + * implicit object in tag files. Declare _jspx_page_context, so we can + * share the code generator with JSPs. + */ + out.printil("PageContext _jspx_page_context = (PageContext)jspContext;"); + + // Declare implicit objects. + out.printil("HttpServletRequest request = " + + "(HttpServletRequest) _jspx_page_context.getRequest();"); + out.printil("HttpServletResponse response = " + + "(HttpServletResponse) _jspx_page_context.getResponse();"); + out.printil("HttpSession session = _jspx_page_context.getSession();"); + out.printil("ServletContext application = _jspx_page_context.getServletContext();"); + out.printil("ServletConfig config = _jspx_page_context.getServletConfig();"); + out.printil("JspWriter out = jspContext.getOut();"); + out.printil("_jspInit(config);"); + + // set current JspContext on ELContext + out.printil("jspContext.getELContext().putContext(JspContext.class,jspContext);"); + + generatePageScopedVariables(tagInfo); + + declareTemporaryScriptingVars(tag); + out.println(); + + out.printil("try {"); + out.pushIndent(); + } + + private void generateTagHandlerPostamble(TagInfo tagInfo) { + out.popIndent(); + + // Have to catch Throwable because a classic tag handler + // helper method is declared to throw Throwable. + out.printil("} catch( Throwable t ) {"); + out.pushIndent(); + out.printil("if( t instanceof SkipPageException )"); + out.printil(" throw (SkipPageException) t;"); + out.printil("if( t instanceof java.io.IOException )"); + out.printil(" throw (java.io.IOException) t;"); + out.printil("if( t instanceof IllegalStateException )"); + out.printil(" throw (IllegalStateException) t;"); + out.printil("if( t instanceof JspException )"); + out.printil(" throw (JspException) t;"); + out.printil("throw new JspException(t);"); + out.popIndent(); + out.printil("} finally {"); + out.pushIndent(); + + // handle restoring VariableMapper + TagAttributeInfo[] attrInfos = tagInfo.getAttributes(); + for (int i = 0; i < attrInfos.length; i++) { + if (attrInfos[i].isDeferredMethod() || attrInfos[i].isDeferredValue()) { + out.printin("_el_variablemapper.setVariable("); + out.print(quote(attrInfos[i].getName())); + out.print(",_el_ve"); + out.print(i); + out.println(");"); + } + } + + // restore nested JspContext on ELContext + out.printil("jspContext.getELContext().putContext(JspContext.class,super.getJspContext());"); + + out.printil("((org.apache.jasper.runtime.JspContextWrapper) jspContext).syncEndTagFile();"); + if (isPoolingEnabled && !tagHandlerPoolNames.isEmpty()) { + out.printil("_jspDestroy();"); + } + out.popIndent(); + out.printil("}"); + + // Close the doTag method + out.popIndent(); + out.printil("}"); + + // Generated methods, helper classes, etc. + genCommonPostamble(); + } + + /** + * Generates declarations for tag handler attributes, and defines the getter + * and setter methods for each. + */ + private void generateTagHandlerAttributes(TagInfo tagInfo) + throws JasperException { + + if (tagInfo.hasDynamicAttributes()) { + out.printil("private java.util.HashMap _jspx_dynamic_attrs = new java.util.HashMap();"); + } + + // Declare attributes + TagAttributeInfo[] attrInfos = tagInfo.getAttributes(); + for (int i = 0; i < attrInfos.length; i++) { + out.printin("private "); + if (attrInfos[i].isFragment()) { + out.print("javax.servlet.jsp.tagext.JspFragment "); + } else { + out.print(JspUtil.toJavaSourceType(attrInfos[i].getTypeName())); + out.print(" "); + } + out.print(attrInfos[i].getName()); + out.println(";"); + } + out.println(); + + // Define attribute getter and setter methods + if (attrInfos != null) { + for (int i = 0; i < attrInfos.length; i++) { + // getter method + out.printin("public "); + if (attrInfos[i].isFragment()) { + out.print("javax.servlet.jsp.tagext.JspFragment "); + } else { + out.print(JspUtil.toJavaSourceType(attrInfos[i] + .getTypeName())); + out.print(" "); + } + out.print(toGetterMethod(attrInfos[i].getName())); + out.println(" {"); + out.pushIndent(); + out.printin("return this."); + out.print(attrInfos[i].getName()); + out.println(";"); + out.popIndent(); + out.printil("}"); + out.println(); + + // setter method + out.printin("public void "); + out.print(toSetterMethodName(attrInfos[i].getName())); + if (attrInfos[i].isFragment()) { + out.print("(javax.servlet.jsp.tagext.JspFragment "); + } else { + out.print("("); + out.print(JspUtil.toJavaSourceType(attrInfos[i] + .getTypeName())); + out.print(" "); + } + out.print(attrInfos[i].getName()); + out.println(") {"); + out.pushIndent(); + out.printin("this."); + out.print(attrInfos[i].getName()); + out.print(" = "); + out.print(attrInfos[i].getName()); + out.println(";"); + if (ctxt.isTagFile()) { + // Tag files should also set jspContext attributes + out.printin("jspContext.setAttribute(\""); + out.print(attrInfos[i].getName()); + out.print("\", "); + out.print(attrInfos[i].getName()); + out.println(");"); + } + out.popIndent(); + out.printil("}"); + out.println(); + } + } + } + + /* + * Generate setter for JspContext so we can create a wrapper and store both + * the original and the wrapper. We need the wrapper to mask the page + * context from the tag file and simulate a fresh page context. We need the + * original to do things like sync AT_BEGIN and AT_END scripting variables. + */ + private void generateSetJspContext(TagInfo tagInfo) { + + boolean nestedSeen = false; + boolean atBeginSeen = false; + boolean atEndSeen = false; + + // Determine if there are any aliases + boolean aliasSeen = false; + TagVariableInfo[] tagVars = tagInfo.getTagVariableInfos(); + for (int i = 0; i < tagVars.length; i++) { + if (tagVars[i].getNameFromAttribute() != null + && tagVars[i].getNameGiven() != null) { + aliasSeen = true; + break; + } + } + + if (aliasSeen) { + out + .printil("public void setJspContext(JspContext ctx, java.util.Map aliasMap) {"); + } else { + out.printil("public void setJspContext(JspContext ctx) {"); + } + out.pushIndent(); + out.printil("super.setJspContext(ctx);"); + out.printil("java.util.ArrayList _jspx_nested = null;"); + out.printil("java.util.ArrayList _jspx_at_begin = null;"); + out.printil("java.util.ArrayList _jspx_at_end = null;"); + + for (int i = 0; i < tagVars.length; i++) { + + switch (tagVars[i].getScope()) { + case VariableInfo.NESTED: + if (!nestedSeen) { + out.printil("_jspx_nested = new java.util.ArrayList();"); + nestedSeen = true; + } + out.printin("_jspx_nested.add("); + break; + + case VariableInfo.AT_BEGIN: + if (!atBeginSeen) { + out.printil("_jspx_at_begin = new java.util.ArrayList();"); + atBeginSeen = true; + } + out.printin("_jspx_at_begin.add("); + break; + + case VariableInfo.AT_END: + if (!atEndSeen) { + out.printil("_jspx_at_end = new java.util.ArrayList();"); + atEndSeen = true; + } + out.printin("_jspx_at_end.add("); + break; + } // switch + + out.print(quote(tagVars[i].getNameGiven())); + out.println(");"); + } + if (aliasSeen) { + out + .printil("this.jspContext = new org.apache.jasper.runtime.JspContextWrapper(ctx, _jspx_nested, _jspx_at_begin, _jspx_at_end, aliasMap);"); + } else { + out + .printil("this.jspContext = new org.apache.jasper.runtime.JspContextWrapper(ctx, _jspx_nested, _jspx_at_begin, _jspx_at_end, null);"); + } + out.popIndent(); + out.printil("}"); + out.println(); + out.printil("public JspContext getJspContext() {"); + out.pushIndent(); + out.printil("return this.jspContext;"); + out.popIndent(); + out.printil("}"); + } + + /* + * Generates implementation of + * javax.servlet.jsp.tagext.DynamicAttributes.setDynamicAttribute() method, + * which saves each dynamic attribute that is passed in so that a scoped + * variable can later be created for it. + */ + public void generateSetDynamicAttribute() { + out + .printil("public void setDynamicAttribute(String uri, String localName, Object value) throws JspException {"); + out.pushIndent(); + /* + * According to the spec, only dynamic attributes with no uri are to be + * present in the Map; all other dynamic attributes are ignored. + */ + out.printil("if (uri == null)"); + out.pushIndent(); + out.printil("_jspx_dynamic_attrs.put(localName, value);"); + out.popIndent(); + out.popIndent(); + out.printil("}"); + } + + /* + * Creates a page-scoped variable for each declared tag attribute. Also, if + * the tag accepts dynamic attributes, a page-scoped variable is made + * available for each dynamic attribute that was passed in. + */ + private void generatePageScopedVariables(JasperTagInfo tagInfo) { + + // "normal" attributes + TagAttributeInfo[] attrInfos = tagInfo.getAttributes(); + boolean variableMapperVar = false; + for (int i = 0; i < attrInfos.length; i++) { + String attrName = attrInfos[i].getName(); + + // handle assigning deferred vars to VariableMapper, storing + // previous values under '_el_ve[i]' for later re-assignment + if (attrInfos[i].isDeferredValue() || attrInfos[i].isDeferredMethod()) { + + // we need to scope the modified VariableMapper for consistency and performance + if (!variableMapperVar) { + out.printil("javax.el.VariableMapper _el_variablemapper = jspContext.getELContext().getVariableMapper();"); + variableMapperVar = true; + } + + out.printin("javax.el.ValueExpression _el_ve"); + out.print(i); + out.print(" = _el_variablemapper.setVariable("); + out.print(quote(attrName)); + out.print(','); + if (attrInfos[i].isDeferredMethod()) { + out.print(VAR_EXPRESSIONFACTORY); + out.print(".createValueExpression("); + out.print(toGetterMethod(attrName)); + out.print(",javax.el.MethodExpression.class)"); + } else { + out.print(toGetterMethod(attrName)); + } + out.println(");"); + } else { + out.printil("if( " + toGetterMethod(attrName) + " != null ) "); + out.pushIndent(); + out.printin("_jspx_page_context.setAttribute("); + out.print(quote(attrName)); + out.print(", "); + out.print(toGetterMethod(attrName)); + out.println(");"); + out.popIndent(); + } + } + + // Expose the Map containing dynamic attributes as a page-scoped var + if (tagInfo.hasDynamicAttributes()) { + out.printin("_jspx_page_context.setAttribute(\""); + out.print(tagInfo.getDynamicAttributesMapName()); + out.print("\", _jspx_dynamic_attrs);"); + } + } + + /* + * Generates the getter method for the given attribute name. + */ + private String toGetterMethod(String attrName) { + char[] attrChars = attrName.toCharArray(); + attrChars[0] = Character.toUpperCase(attrChars[0]); + return "get" + new String(attrChars) + "()"; + } + + /* + * Generates the setter method name for the given attribute name. + */ + private String toSetterMethodName(String attrName) { + char[] attrChars = attrName.toCharArray(); + attrChars[0] = Character.toUpperCase(attrChars[0]); + return "set" + new String(attrChars); + } + + /** + * Class storing the result of introspecting a custom tag handler. + */ + private static class TagHandlerInfo { + + private Hashtable methodMaps; + + private Hashtable propertyEditorMaps; + + private Class tagHandlerClass; + + /** + * Constructor. + * + * @param n + * The custom tag whose tag handler class is to be + * introspected + * @param tagHandlerClass + * Tag handler class + * @param err + * Error dispatcher + */ + TagHandlerInfo(Node n, Class tagHandlerClass, ErrorDispatcher err) + throws JasperException { + this.tagHandlerClass = tagHandlerClass; + this.methodMaps = new Hashtable(); + this.propertyEditorMaps = new Hashtable(); + + try { + BeanInfo tagClassInfo = Introspector + .getBeanInfo(tagHandlerClass); + PropertyDescriptor[] pd = tagClassInfo.getPropertyDescriptors(); + for (int i = 0; i < pd.length; i++) { + /* + * FIXME: should probably be checking for things like + * pageContext, bodyContent, and parent here -akv + */ + if (pd[i].getWriteMethod() != null) { + methodMaps.put(pd[i].getName(), pd[i].getWriteMethod()); + } + if (pd[i].getPropertyEditorClass() != null) + propertyEditorMaps.put(pd[i].getName(), pd[i] + .getPropertyEditorClass()); + } + } catch (IntrospectionException ie) { + err.jspError(n, "jsp.error.introspect.taghandler", + tagHandlerClass.getName(), ie); + } + } + + /** + * XXX + */ + public Method getSetterMethod(String attrName) { + return (Method) methodMaps.get(attrName); + } + + /** + * XXX + */ + public Class getPropertyEditorClass(String attrName) { + return (Class) propertyEditorMaps.get(attrName); + } + + /** + * XXX + */ + public Class getTagHandlerClass() { + return tagHandlerClass; + } + } + + /** + * A class for generating codes to a buffer. Included here are some support + * for tracking source to Java lines mapping. + */ + private static class GenBuffer { + + /* + * For a CustomTag, the codes that are generated at the beginning of the + * tag may not be in the same buffer as those for the body of the tag. + * Two fields are used here to keep this straight. For codes that do not + * corresponds to any JSP lines, they should be null. + */ + private Node node; + + private Node.Nodes body; + + private java.io.CharArrayWriter charWriter; + + protected ServletWriter out; + + GenBuffer() { + this(null, null); + } + + GenBuffer(Node n, Node.Nodes b) { + node = n; + body = b; + if (body != null) { + body.setGeneratedInBuffer(true); + } + charWriter = new java.io.CharArrayWriter(); + out = new ServletWriter(new java.io.PrintWriter(charWriter)); + } + + public ServletWriter getOut() { + return out; + } + + public String toString() { + return charWriter.toString(); + } + + /** + * Adjust the Java Lines. This is necessary because the Java lines + * stored with the nodes are relative the beginning of this buffer and + * need to be adjusted when this buffer is inserted into the source. + */ + public void adjustJavaLines(final int offset) { + + if (node != null) { + adjustJavaLine(node, offset); + } + + if (body != null) { + try { + body.visit(new Node.Visitor() { + + public void doVisit(Node n) { + adjustJavaLine(n, offset); + } + + public void visit(Node.CustomTag n) + throws JasperException { + Node.Nodes b = n.getBody(); + if (b != null && !b.isGeneratedInBuffer()) { + // Don't adjust lines for the nested tags that + // are also generated in buffers, because the + // adjustments will be done elsewhere. + b.visit(this); + } + } + }); + } catch (JasperException ex) { + } + } + } + + private static void adjustJavaLine(Node n, int offset) { + if (n.getBeginJavaLine() > 0) { + n.setBeginJavaLine(n.getBeginJavaLine() + offset); + n.setEndJavaLine(n.getEndJavaLine() + offset); + } + } + } + + /** + * Keeps track of the generated Fragment Helper Class + */ + private static class FragmentHelperClass { + + private static class Fragment { + private GenBuffer genBuffer; + + private int id; + + public Fragment(int id, Node node) { + this.id = id; + genBuffer = new GenBuffer(null, node.getBody()); + } + + public GenBuffer getGenBuffer() { + return this.genBuffer; + } + + public int getId() { + return this.id; + } + } + + // True if the helper class should be generated. + private boolean used = false; + + private ArrayList fragments = new ArrayList(); + + private String className; + + // Buffer for entire helper class + private GenBuffer classBuffer = new GenBuffer(); + + public FragmentHelperClass(String className) { + this.className = className; + } + + public String getClassName() { + return this.className; + } + + public boolean isUsed() { + return this.used; + } + + public void generatePreamble() { + ServletWriter out = this.classBuffer.getOut(); + out.println(); + out.pushIndent(); + // Note: cannot be static, as we need to reference things like + // _jspx_meth_* + out.printil("private class " + className); + out.printil(" extends " + + "org.apache.jasper.runtime.JspFragmentHelper"); + out.printil("{"); + out.pushIndent(); + out + .printil("private javax.servlet.jsp.tagext.JspTag _jspx_parent;"); + out.printil("private int[] _jspx_push_body_count;"); + out.println(); + out.printil("public " + className + + "( int discriminator, JspContext jspContext, " + + "javax.servlet.jsp.tagext.JspTag _jspx_parent, " + + "int[] _jspx_push_body_count ) {"); + out.pushIndent(); + out.printil("super( discriminator, jspContext, _jspx_parent );"); + out.printil("this._jspx_parent = _jspx_parent;"); + out.printil("this._jspx_push_body_count = _jspx_push_body_count;"); + out.popIndent(); + out.printil("}"); + } + + public Fragment openFragment(Node parent, String tagHandlerVar, + int methodNesting) throws JasperException { + Fragment result = new Fragment(fragments.size(), parent); + fragments.add(result); + this.used = true; + parent.setInnerClassName(className); + + ServletWriter out = result.getGenBuffer().getOut(); + out.pushIndent(); + out.pushIndent(); + // XXX - Returns boolean because if a tag is invoked from + // within this fragment, the Generator sometimes might + // generate code like "return true". This is ignored for now, + // meaning only the fragment is skipped. The JSR-152 + // expert group is currently discussing what to do in this case. + // See comment in closeFragment() + if (methodNesting > 0) { + out.printin("public boolean invoke"); + } else { + out.printin("public void invoke"); + } + out.println(result.getId() + "( " + "JspWriter out ) "); + out.pushIndent(); + // Note: Throwable required because methods like _jspx_meth_* + // throw Throwable. + out.printil("throws Throwable"); + out.popIndent(); + out.printil("{"); + out.pushIndent(); + generateLocalVariables(out, parent); + + return result; + } + + public void closeFragment(Fragment fragment, int methodNesting) { + ServletWriter out = fragment.getGenBuffer().getOut(); + // XXX - See comment in openFragment() + if (methodNesting > 0) { + out.printil("return false;"); + } else { + out.printil("return;"); + } + out.popIndent(); + out.printil("}"); + } + + public void generatePostamble() { + ServletWriter out = this.classBuffer.getOut(); + // Generate all fragment methods: + for (int i = 0; i < fragments.size(); i++) { + Fragment fragment = (Fragment) fragments.get(i); + fragment.getGenBuffer().adjustJavaLines(out.getJavaLine() - 1); + out.printMultiLn(fragment.getGenBuffer().toString()); + } + + // Generate postamble: + out.printil("public void invoke( java.io.Writer writer )"); + out.pushIndent(); + out.printil("throws JspException"); + out.popIndent(); + out.printil("{"); + out.pushIndent(); + out.printil("JspWriter out = null;"); + out.printil("if( writer != null ) {"); + out.pushIndent(); + out.printil("out = this.jspContext.pushBody(writer);"); + out.popIndent(); + out.printil("} else {"); + out.pushIndent(); + out.printil("out = this.jspContext.getOut();"); + out.popIndent(); + out.printil("}"); + out.printil("try {"); + out.pushIndent(); + out.printil("this.jspContext.getELContext().putContext(JspContext.class,this.jspContext);"); + out.printil("switch( this.discriminator ) {"); + out.pushIndent(); + for (int i = 0; i < fragments.size(); i++) { + out.printil("case " + i + ":"); + out.pushIndent(); + out.printil("invoke" + i + "( out );"); + out.printil("break;"); + out.popIndent(); + } + out.popIndent(); + out.printil("}"); // switch + out.popIndent(); + out.printil("}"); // try + out.printil("catch( Throwable e ) {"); + out.pushIndent(); + out.printil("if (e instanceof SkipPageException)"); + out.printil(" throw (SkipPageException) e;"); + out.printil("throw new JspException( e );"); + out.popIndent(); + out.printil("}"); // catch + out.printil("finally {"); + out.pushIndent(); + + out.printil("if( writer != null ) {"); + out.pushIndent(); + out.printil("this.jspContext.popBody();"); + out.popIndent(); + out.printil("}"); + + out.popIndent(); + out.printil("}"); // finally + out.popIndent(); + out.printil("}"); // invoke method + out.popIndent(); + out.printil("}"); // helper class + out.popIndent(); + } + + public String toString() { + return classBuffer.toString(); + } + + public void adjustJavaLines(int offset) { + for (int i = 0; i < fragments.size(); i++) { + Fragment fragment = (Fragment) fragments.get(i); + fragment.getGenBuffer().adjustJavaLines(offset); + } + } + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ImplicitTagLibraryInfo.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ImplicitTagLibraryInfo.java new file mode 100644 index 000000000..de41ab545 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ImplicitTagLibraryInfo.java @@ -0,0 +1,218 @@ +/* + * 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.InputStream; +import java.util.Collection; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Set; +import java.util.Vector; + +import javax.servlet.jsp.tagext.FunctionInfo; +import javax.servlet.jsp.tagext.TagFileInfo; +import javax.servlet.jsp.tagext.TagInfo; +import javax.servlet.jsp.tagext.TagLibraryInfo; + +import org.apache.jasper.JasperException; +import org.apache.jasper.JspCompilationContext; +import org.apache.jasper.xmlparser.ParserUtils; +import org.apache.jasper.xmlparser.TreeNode; + +/** + * Class responsible for generating an implicit tag library containing tag + * handlers corresponding to the tag files in "/WEB-INF/tags/" or a + * subdirectory of it. + * + * @author Jan Luehe + */ +class ImplicitTagLibraryInfo extends TagLibraryInfo { + + private static final String WEB_INF_TAGS = "/WEB-INF/tags"; + private static final String TAG_FILE_SUFFIX = ".tag"; + private static final String TAGX_FILE_SUFFIX = ".tagx"; + private static final String TAGS_SHORTNAME = "tags"; + private static final String TLIB_VERSION = "1.0"; + private static final String JSP_VERSION = "2.0"; + private static final String IMPLICIT_TLD = "implicit.tld"; + + // Maps tag names to tag file paths + private Hashtable tagFileMap; + + private ParserController pc; + private PageInfo pi; + private Vector vec; + + /** + * Constructor. + */ + public ImplicitTagLibraryInfo(JspCompilationContext ctxt, + ParserController pc, + PageInfo pi, + String prefix, + String tagdir, + ErrorDispatcher err) throws JasperException { + super(prefix, null); + this.pc = pc; + this.pi = pi; + this.tagFileMap = new Hashtable(); + this.vec = new Vector(); + + // Implicit tag libraries have no functions: + this.functions = new FunctionInfo[0]; + + tlibversion = TLIB_VERSION; + jspversion = JSP_VERSION; + + if (!tagdir.startsWith(WEB_INF_TAGS)) { + err.jspError("jsp.error.invalid.tagdir", tagdir); + } + + // Determine the value of the subelement of the + // "imaginary" element + if (tagdir.equals(WEB_INF_TAGS) + || tagdir.equals( WEB_INF_TAGS + "/")) { + shortname = TAGS_SHORTNAME; + } else { + shortname = tagdir.substring(WEB_INF_TAGS.length()); + shortname = shortname.replace('/', '-'); + } + + // Populate mapping of tag names to tag file paths + Set dirList = ctxt.getResourcePaths(tagdir); + if (dirList != null) { + Iterator it = dirList.iterator(); + while (it.hasNext()) { + String path = (String) it.next(); + if (path.endsWith(TAG_FILE_SUFFIX) + || path.endsWith(TAGX_FILE_SUFFIX)) { + /* + * Use the filename of the tag file, without the .tag or + * .tagx extension, respectively, as the subelement + * of the "imaginary" element + */ + String suffix = path.endsWith(TAG_FILE_SUFFIX) ? + TAG_FILE_SUFFIX : TAGX_FILE_SUFFIX; + String tagName = path.substring(path.lastIndexOf("/") + 1); + tagName = tagName.substring(0, + tagName.lastIndexOf(suffix)); + tagFileMap.put(tagName, path); + } else if (path.endsWith(IMPLICIT_TLD)) { + InputStream in = null; + try { + in = ctxt.getResourceAsStream(path); + if (in != null) { + + // Add implicit TLD to dependency list + if (pi != null) { + pi.addDependant(path); + } + + ParserUtils pu = new ParserUtils(); + TreeNode tld = pu.parseXMLDocument(uri, in); + + if (tld.findAttribute("version") != null) { + this.jspversion = tld.findAttribute("version"); + } + + // Process each child element of our element + Iterator list = tld.findChildren(); + + while (list.hasNext()) { + TreeNode element = (TreeNode) list.next(); + String tname = element.getName(); + + if ("tlibversion".equals(tname) // JSP 1.1 + || "tlib-version".equals(tname)) { // JSP 1.2 + this.tlibversion = element.getBody(); + } else if ("jspversion".equals(tname) + || "jsp-version".equals(tname)) { + this.jspversion = element.getBody(); + } else if ("shortname".equals(tname) || "short-name".equals(tname)) { + // Ignore + } else { + // All other elements are invalid + err.jspError("jsp.error.invalid.implicit", path); + } + } + try { + double version = Double.parseDouble(this.jspversion); + if (version < 2.0) { + err.jspError("jsp.error.invalid.implicit.version", path); + } + } catch (NumberFormatException e) { + err.jspError("jsp.error.invalid.implicit.version", path); + } + } + } finally { + if (in != null) { + try { + in.close(); + } catch (Throwable t) { + } + } + } + } + } + } + + } + + /** + * Checks to see if the given tag name maps to a tag file path, + * and if so, parses the corresponding tag file. + * + * @return The TagFileInfo corresponding to the given tag name, or null if + * the given tag name is not implemented as a tag file + */ + public TagFileInfo getTagFile(String shortName) { + + TagFileInfo tagFile = super.getTagFile(shortName); + if (tagFile == null) { + String path = (String) tagFileMap.get(shortName); + if (path == null) { + return null; + } + + TagInfo tagInfo = null; + try { + tagInfo = TagFileProcessor.parseTagFileDirectives(pc, + shortName, + path, + pc.getJspCompilationContext().getTagFileJarUrl(path), + this); + } catch (JasperException je) { + throw new RuntimeException(je.toString(), je); + } + + tagFile = new TagFileInfo(shortName, path, tagInfo); + vec.addElement(tagFile); + + this.tagFiles = new TagFileInfo[vec.size()]; + vec.copyInto(this.tagFiles); + } + + return tagFile; + } + + public TagLibraryInfo[] getTagLibraryInfos() { + Collection coll = pi.getTaglibs(); + return (TagLibraryInfo[]) coll.toArray(new TagLibraryInfo[0]); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JDTCompiler.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JDTCompiler.java new file mode 100644 index 000000000..fea1147b8 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JDTCompiler.java @@ -0,0 +1,460 @@ +/* + * 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.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.StringTokenizer; + +import org.apache.jasper.JasperException; +import org.eclipse.jdt.core.compiler.IProblem; +import org.eclipse.jdt.internal.compiler.ClassFile; +import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.Compiler; +import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; +import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; +import org.eclipse.jdt.internal.compiler.IProblemFactory; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; +import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; +import org.eclipse.jdt.internal.compiler.env.INameEnvironment; +import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; + +/** + * JDT class compiler. This compiler will load source dependencies from the + * context classloader, reducing dramatically disk access during + * the compilation process. + * + * @author Cocoon2 + * @author Remy Maucherat + */ +public class JDTCompiler extends org.apache.jasper.compiler.Compiler { + + + /** + * Compile the servlet from .java file to .class file + */ + protected void generateClass(String[] smap) + throws FileNotFoundException, JasperException, Exception { + + long t1 = 0; + if (log.isDebugEnabled()) { + t1 = System.currentTimeMillis(); + } + + final String sourceFile = ctxt.getServletJavaFileName(); + final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath(); + String packageName = ctxt.getServletPackageName(); + final String targetClassName = + ((packageName.length() != 0) ? (packageName + ".") : "") + + ctxt.getServletClassName(); + final ClassLoader classLoader = ctxt.getJspLoader(); + String[] fileNames = new String[] {sourceFile}; + String[] classNames = new String[] {targetClassName}; + final ArrayList problemList = new ArrayList(); + + class CompilationUnit implements ICompilationUnit { + + String className; + String sourceFile; + + CompilationUnit(String sourceFile, String className) { + this.className = className; + this.sourceFile = sourceFile; + } + + public char[] getFileName() { + return sourceFile.toCharArray(); + } + + public char[] getContents() { + char[] result = null; + FileInputStream is = null; + try { + is = new FileInputStream(sourceFile); + Reader reader = + new BufferedReader(new InputStreamReader(is, ctxt.getOptions().getJavaEncoding())); + if (reader != null) { + char[] chars = new char[8192]; + StringBuffer buf = new StringBuffer(); + int count; + while ((count = reader.read(chars, 0, + chars.length)) > 0) { + buf.append(chars, 0, count); + } + result = new char[buf.length()]; + buf.getChars(0, result.length, result, 0); + } + } catch (IOException e) { + log.error("Compilation error", e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException exc) { + // Ignore + } + } + } + return result; + } + + public char[] getMainTypeName() { + int dot = className.lastIndexOf('.'); + if (dot > 0) { + return className.substring(dot + 1).toCharArray(); + } + return className.toCharArray(); + } + + public char[][] getPackageName() { + StringTokenizer izer = + new StringTokenizer(className, "."); + char[][] result = new char[izer.countTokens()-1][]; + for (int i = 0; i < result.length; i++) { + String tok = izer.nextToken(); + result[i] = tok.toCharArray(); + } + return result; + } + } + + final INameEnvironment env = new INameEnvironment() { + + public NameEnvironmentAnswer + findType(char[][] compoundTypeName) { + String result = ""; + String sep = ""; + for (int i = 0; i < compoundTypeName.length; i++) { + result += sep; + result += new String(compoundTypeName[i]); + sep = "."; + } + return findType(result); + } + + public NameEnvironmentAnswer + findType(char[] typeName, + char[][] packageName) { + String result = ""; + String sep = ""; + for (int i = 0; i < packageName.length; i++) { + result += sep; + result += new String(packageName[i]); + sep = "."; + } + result += sep; + result += new String(typeName); + return findType(result); + } + + private NameEnvironmentAnswer findType(String className) { + + InputStream is = null; + try { + if (className.equals(targetClassName)) { + ICompilationUnit compilationUnit = + new CompilationUnit(sourceFile, className); + return + new NameEnvironmentAnswer(compilationUnit, null); + } + String resourceName = + className.replace('.', '/') + ".class"; + is = classLoader.getResourceAsStream(resourceName); + if (is != null) { + byte[] classBytes; + byte[] buf = new byte[8192]; + ByteArrayOutputStream baos = + new ByteArrayOutputStream(buf.length); + int count; + while ((count = is.read(buf, 0, buf.length)) > 0) { + baos.write(buf, 0, count); + } + baos.flush(); + classBytes = baos.toByteArray(); + char[] fileName = className.toCharArray(); + ClassFileReader classFileReader = + new ClassFileReader(classBytes, fileName, + true); + return + new NameEnvironmentAnswer(classFileReader, null); + } + } catch (IOException exc) { + log.error("Compilation error", exc); + } catch (org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException exc) { + log.error("Compilation error", exc); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException exc) { + // Ignore + } + } + } + return null; + } + + private boolean isPackage(String result) { + if (result.equals(targetClassName)) { + return false; + } + String resourceName = result.replace('.', '/') + ".class"; + InputStream is = + classLoader.getResourceAsStream(resourceName); + return is == null; + } + + public boolean isPackage(char[][] parentPackageName, + char[] packageName) { + String result = ""; + String sep = ""; + if (parentPackageName != null) { + for (int i = 0; i < parentPackageName.length; i++) { + result += sep; + String str = new String(parentPackageName[i]); + result += str; + sep = "."; + } + } + String str = new String(packageName); + if (Character.isUpperCase(str.charAt(0))) { + if (!isPackage(result)) { + return false; + } + } + result += sep; + result += str; + return isPackage(result); + } + + public void cleanup() { + } + + }; + + final IErrorHandlingPolicy policy = + DefaultErrorHandlingPolicies.proceedWithAllProblems(); + + final Map settings = new HashMap(); + settings.put(CompilerOptions.OPTION_LineNumberAttribute, + CompilerOptions.GENERATE); + settings.put(CompilerOptions.OPTION_SourceFileAttribute, + CompilerOptions.GENERATE); + settings.put(CompilerOptions.OPTION_ReportDeprecation, + CompilerOptions.IGNORE); + if (ctxt.getOptions().getJavaEncoding() != null) { + settings.put(CompilerOptions.OPTION_Encoding, + ctxt.getOptions().getJavaEncoding()); + } + if (ctxt.getOptions().getClassDebugInfo()) { + settings.put(CompilerOptions.OPTION_LocalVariableAttribute, + CompilerOptions.GENERATE); + } + + // Source JVM + if(ctxt.getOptions().getCompilerSourceVM() != null) { + String opt = ctxt.getOptions().getCompilerSourceVM(); + if(opt.equals("1.1")) { + settings.put(CompilerOptions.OPTION_Source, + CompilerOptions.VERSION_1_1); + } else if(opt.equals("1.2")) { + settings.put(CompilerOptions.OPTION_Source, + CompilerOptions.VERSION_1_2); + } else if(opt.equals("1.3")) { + settings.put(CompilerOptions.OPTION_Source, + CompilerOptions.VERSION_1_3); + } else if(opt.equals("1.4")) { + settings.put(CompilerOptions.OPTION_Source, + CompilerOptions.VERSION_1_4); + } else if(opt.equals("1.5")) { + settings.put(CompilerOptions.OPTION_Source, + CompilerOptions.VERSION_1_5); + } else if(opt.equals("1.6")) { + settings.put(CompilerOptions.OPTION_Source, + CompilerOptions.VERSION_1_6); + } else if(opt.equals("1.7")) { + settings.put(CompilerOptions.OPTION_Source, + CompilerOptions.VERSION_1_7); + } else { + log.warn("Unknown source VM " + opt + " ignored."); + settings.put(CompilerOptions.OPTION_Source, + CompilerOptions.VERSION_1_5); + } + } else { + // Default to 1.5 + settings.put(CompilerOptions.OPTION_Source, + CompilerOptions.VERSION_1_5); + } + + // Target JVM + if(ctxt.getOptions().getCompilerTargetVM() != null) { + String opt = ctxt.getOptions().getCompilerTargetVM(); + if(opt.equals("1.1")) { + settings.put(CompilerOptions.OPTION_TargetPlatform, + CompilerOptions.VERSION_1_1); + } else if(opt.equals("1.2")) { + settings.put(CompilerOptions.OPTION_TargetPlatform, + CompilerOptions.VERSION_1_2); + } else if(opt.equals("1.3")) { + settings.put(CompilerOptions.OPTION_TargetPlatform, + CompilerOptions.VERSION_1_3); + } else if(opt.equals("1.4")) { + settings.put(CompilerOptions.OPTION_TargetPlatform, + CompilerOptions.VERSION_1_4); + } else if(opt.equals("1.5")) { + settings.put(CompilerOptions.OPTION_TargetPlatform, + CompilerOptions.VERSION_1_5); + settings.put(CompilerOptions.OPTION_Compliance, + CompilerOptions.VERSION_1_5); + } else if(opt.equals("1.6")) { + settings.put(CompilerOptions.OPTION_TargetPlatform, + CompilerOptions.VERSION_1_6); + settings.put(CompilerOptions.OPTION_Compliance, + CompilerOptions.VERSION_1_6); + } else if(opt.equals("1.7")) { + settings.put(CompilerOptions.OPTION_TargetPlatform, + CompilerOptions.VERSION_1_7); + settings.put(CompilerOptions.OPTION_Compliance, + CompilerOptions.VERSION_1_7); + } else { + log.warn("Unknown target VM " + opt + " ignored."); + settings.put(CompilerOptions.OPTION_TargetPlatform, + CompilerOptions.VERSION_1_5); + } + } else { + // Default to 1.5 + settings.put(CompilerOptions.OPTION_TargetPlatform, + CompilerOptions.VERSION_1_5); + settings.put(CompilerOptions.OPTION_Compliance, + CompilerOptions.VERSION_1_5); + } + + final IProblemFactory problemFactory = + new DefaultProblemFactory(Locale.getDefault()); + + final ICompilerRequestor requestor = new ICompilerRequestor() { + public void acceptResult(CompilationResult result) { + try { + if (result.hasProblems()) { + IProblem[] problems = result.getProblems(); + for (int i = 0; i < problems.length; i++) { + IProblem problem = problems[i]; + if (problem.isError()) { + String name = + new String(problems[i].getOriginatingFileName()); + try { + problemList.add(ErrorDispatcher.createJavacError + (name, pageNodes, new StringBuffer(problem.getMessage()), + problem.getSourceLineNumber(), ctxt)); + } catch (JasperException e) { + log.error("Error visiting node", e); + } + } + } + } + if (problemList.isEmpty()) { + ClassFile[] classFiles = result.getClassFiles(); + for (int i = 0; i < classFiles.length; i++) { + ClassFile classFile = classFiles[i]; + char[][] compoundName = + classFile.getCompoundName(); + String className = ""; + String sep = ""; + for (int j = 0; + j < compoundName.length; j++) { + className += sep; + className += new String(compoundName[j]); + sep = "."; + } + byte[] bytes = classFile.getBytes(); + String outFile = outputDir + "/" + + className.replace('.', '/') + ".class"; + FileOutputStream fout = + new FileOutputStream(outFile); + BufferedOutputStream bos = + new BufferedOutputStream(fout); + bos.write(bytes); + bos.close(); + } + } + } catch (IOException exc) { + log.error("Compilation error", exc); + } + } + }; + + ICompilationUnit[] compilationUnits = + new ICompilationUnit[classNames.length]; + for (int i = 0; i < compilationUnits.length; i++) { + String className = classNames[i]; + compilationUnits[i] = new CompilationUnit(fileNames[i], className); + } + Compiler compiler = new Compiler(env, + policy, + settings, + requestor, + problemFactory, + true); + compiler.compile(compilationUnits); + + if (!ctxt.keepGenerated()) { + File javaFile = new File(ctxt.getServletJavaFileName()); + javaFile.delete(); + } + + if (!problemList.isEmpty()) { + JavacErrorDetail[] jeds = + (JavacErrorDetail[]) problemList.toArray(new JavacErrorDetail[0]); + errDispatcher.javacError(jeds); + } + + if( log.isDebugEnabled() ) { + long t2=System.currentTimeMillis(); + log.debug("Compiled " + ctxt.getServletJavaFileName() + " " + + (t2-t1) + "ms"); + } + + if (ctxt.isPrototypeMode()) { + return; + } + + // JSR45 Support + if (! options.isSmapSuppressed()) { + SmapUtil.installSmap(smap); + } + + } + + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JasperTagInfo.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JasperTagInfo.java new file mode 100644 index 000000000..8586d5e60 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JasperTagInfo.java @@ -0,0 +1,58 @@ +/* + * 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 javax.servlet.jsp.tagext.*; + +/** + * TagInfo extension used by tag handlers that are implemented via tag files. + * This class provides access to the name of the Map used to store the + * dynamic attribute names and values passed to the custom action invocation. + * This information is used by the code generator. + */ +class JasperTagInfo extends TagInfo { + + private String dynamicAttrsMapName; + + public JasperTagInfo(String tagName, + String tagClassName, + String bodyContent, + String infoString, + TagLibraryInfo taglib, + TagExtraInfo tagExtraInfo, + TagAttributeInfo[] attributeInfo, + String displayName, + String smallIcon, + String largeIcon, + TagVariableInfo[] tvi, + String mapName) { + + super(tagName, tagClassName, bodyContent, infoString, taglib, + tagExtraInfo, attributeInfo, displayName, smallIcon, largeIcon, + tvi); + this.dynamicAttrsMapName = mapName; + } + + public String getDynamicAttributesMapName() { + return dynamicAttrsMapName; + } + + public boolean hasDynamicAttributes() { + return dynamicAttrsMapName != null; + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JavacErrorDetail.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JavacErrorDetail.java new file mode 100644 index 000000000..cf2daffe1 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JavacErrorDetail.java @@ -0,0 +1,232 @@ +/* + * 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.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import org.apache.jasper.JspCompilationContext; + +/** + * Class providing details about a javac compilation error. + * + * @author Jan Luehe + * @author Kin-man Chung + */ +public class JavacErrorDetail { + + private String javaFileName; + private int javaLineNum; + private String jspFileName; + private int jspBeginLineNum; + private StringBuffer errMsg; + private String jspExtract = null; + + /** + * Constructor. + * + * @param javaFileName The name of the Java file in which the + * compilation error occurred + * @param javaLineNum The compilation error line number + * @param errMsg The compilation error message + */ + public JavacErrorDetail(String javaFileName, + int javaLineNum, + StringBuffer errMsg) { + + this.javaFileName = javaFileName; + this.javaLineNum = javaLineNum; + this.errMsg = errMsg; + this.jspBeginLineNum = -1; + } + + /** + * Constructor. + * + * @param javaFileName The name of the Java file in which the + * compilation error occurred + * @param javaLineNum The compilation error line number + * @param jspFileName The name of the JSP file from which the Java source + * file was generated + * @param jspBeginLineNum The start line number of the JSP element + * responsible for the compilation error + * @param errMsg The compilation error message + */ + public JavacErrorDetail(String javaFileName, + int javaLineNum, + String jspFileName, + int jspBeginLineNum, + StringBuffer errMsg) { + + this(javaFileName, javaLineNum, jspFileName, jspBeginLineNum, errMsg, + null); + } + + public JavacErrorDetail(String javaFileName, + int javaLineNum, + String jspFileName, + int jspBeginLineNum, + StringBuffer errMsg, + JspCompilationContext ctxt) { + + this(javaFileName, javaLineNum, errMsg); + this.jspFileName = jspFileName; + this.jspBeginLineNum = jspBeginLineNum; + + if (jspBeginLineNum > 0 && ctxt != null) { + InputStream is = null; + FileInputStream fis = null; + + try { + // Read both files in, so we can inspect them + is = ctxt.getResourceAsStream(jspFileName); + String[] jspLines = readFile(is); + + fis = new FileInputStream(ctxt.getServletJavaFileName()); + String[] javaLines = readFile(fis); + + // If the line contains the opening of a multi-line scriptlet + // block, then the JSP line number we got back is probably + // faulty. Scan forward to match the java line... + if (jspLines[jspBeginLineNum-1].lastIndexOf("<%") > + jspLines[jspBeginLineNum-1].lastIndexOf("%>")) { + String javaLine = javaLines[javaLineNum-1].trim(); + + for (int i=jspBeginLineNum-1; i= 0) { + path = urlPattern.substring(0,i+1); + file = urlPattern.substring(i+1); + } else { + file = urlPattern; + } + + // pattern must be "*", or of the form "*.jsp" + if (file.equals("*")) { + extension = "*"; + } else if (file.startsWith("*.")) { + extension = file.substring(file.indexOf('.')+1); + } + + // The url patterns are reconstructed as the follwoing: + // path != null, extension == null: / or /foo/bar.ext + // path == null, extension != null: *.ext + // path != null, extension == "*": /foo/* + boolean isStar = "*".equals(extension); + if ((path == null && (extension == null || isStar)) + || (path != null && !isStar)) { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage( + "jsp.warning.bad.urlpattern.propertygroup", + urlPattern)); + } + continue; + } + } + + JspProperty property = new JspProperty(isXml, + elIgnored, + scriptingInvalid, + pageEncoding, + includePrelude, + includeCoda, + deferredSyntaxAllowedAsLiteral, + trimDirectiveWhitespaces); + JspPropertyGroup propertyGroup = + new JspPropertyGroup(path, extension, property); + + jspProperties.addElement(propertyGroup); + } + } + } catch (Exception ex) { + throw new JasperException(ex); + } finally { + if (is != null) { + try { + is.close(); + } catch (Throwable t) {} + } + } + } + + private void init() throws JasperException { + + if (!initialized) { + processWebDotXml(ctxt); + defaultJspProperty = new JspProperty(defaultIsXml, + defaultIsELIgnored, + defaultIsScriptingInvalid, + null, null, null, defaultDeferedSyntaxAllowedAsLiteral, + defaultTrimDirectiveWhitespaces); + initialized = true; + } + } + + /** + * Select the property group that has more restrictive url-pattern. + * In case of tie, select the first. + */ + private JspPropertyGroup selectProperty(JspPropertyGroup prev, + JspPropertyGroup curr) { + if (prev == null) { + return curr; + } + if (prev.getExtension() == null) { + // exact match + return prev; + } + if (curr.getExtension() == null) { + // exact match + return curr; + } + String prevPath = prev.getPath(); + String currPath = curr.getPath(); + if (prevPath == null && currPath == null) { + // Both specifies a *.ext, keep the first one + return prev; + } + if (prevPath == null && currPath != null) { + return curr; + } + if (prevPath != null && currPath == null) { + return prev; + } + if (prevPath.length() >= currPath.length()) { + return prev; + } + return curr; + } + + + /** + * Find a property that best matches the supplied resource. + * @param uri the resource supplied. + * @return a JspProperty indicating the best match, or some default. + */ + public JspProperty findJspProperty(String uri) throws JasperException { + + init(); + + // JSP Configuration settings do not apply to tag files + if (jspProperties == null || uri.endsWith(".tag") + || uri.endsWith(".tagx")) { + return defaultJspProperty; + } + + String uriPath = null; + int index = uri.lastIndexOf('/'); + if (index >=0 ) { + uriPath = uri.substring(0, index+1); + } + String uriExtension = null; + index = uri.lastIndexOf('.'); + if (index >=0) { + uriExtension = uri.substring(index+1); + } + + Vector includePreludes = new Vector(); + Vector includeCodas = new Vector(); + + JspPropertyGroup isXmlMatch = null; + JspPropertyGroup elIgnoredMatch = null; + JspPropertyGroup scriptingInvalidMatch = null; + JspPropertyGroup pageEncodingMatch = null; + JspPropertyGroup deferedSyntaxAllowedAsLiteralMatch = null; + JspPropertyGroup trimDirectiveWhitespacesMatch = null; + + Iterator iter = jspProperties.iterator(); + while (iter.hasNext()) { + + JspPropertyGroup jpg = (JspPropertyGroup) iter.next(); + JspProperty jp = jpg.getJspProperty(); + + // (arrays will be the same length) + String extension = jpg.getExtension(); + String path = jpg.getPath(); + + if (extension == null) { + // exact match pattern: /a/foo.jsp + if (!uri.equals(path)) { + // not matched; + continue; + } + } else { + // Matching patterns *.ext or /p/* + if (path != null && uriPath != null && + ! uriPath.startsWith(path)) { + // not matched + continue; + } + if (!extension.equals("*") && + !extension.equals(uriExtension)) { + // not matched + continue; + } + } + // We have a match + // Add include-preludes and include-codas + if (jp.getIncludePrelude() != null) { + includePreludes.addAll(jp.getIncludePrelude()); + } + if (jp.getIncludeCoda() != null) { + includeCodas.addAll(jp.getIncludeCoda()); + } + + // If there is a previous match for the same property, remember + // the one that is more restrictive. + if (jp.isXml() != null) { + isXmlMatch = selectProperty(isXmlMatch, jpg); + } + if (jp.isELIgnored() != null) { + elIgnoredMatch = selectProperty(elIgnoredMatch, jpg); + } + if (jp.isScriptingInvalid() != null) { + scriptingInvalidMatch = + selectProperty(scriptingInvalidMatch, jpg); + } + if (jp.getPageEncoding() != null) { + pageEncodingMatch = selectProperty(pageEncodingMatch, jpg); + } + if (jp.isDeferedSyntaxAllowedAsLiteral() != null) { + deferedSyntaxAllowedAsLiteralMatch = + selectProperty(deferedSyntaxAllowedAsLiteralMatch, jpg); + } + if (jp.isTrimDirectiveWhitespaces() != null) { + trimDirectiveWhitespacesMatch = + selectProperty(trimDirectiveWhitespacesMatch, jpg); + } + } + + + String isXml = defaultIsXml; + String isELIgnored = defaultIsELIgnored; + String isScriptingInvalid = defaultIsScriptingInvalid; + String pageEncoding = null; + String isDeferedSyntaxAllowedAsLiteral = defaultDeferedSyntaxAllowedAsLiteral; + String isTrimDirectiveWhitespaces = defaultTrimDirectiveWhitespaces; + + if (isXmlMatch != null) { + isXml = isXmlMatch.getJspProperty().isXml(); + } + if (elIgnoredMatch != null) { + isELIgnored = elIgnoredMatch.getJspProperty().isELIgnored(); + } + if (scriptingInvalidMatch != null) { + isScriptingInvalid = + scriptingInvalidMatch.getJspProperty().isScriptingInvalid(); + } + if (pageEncodingMatch != null) { + pageEncoding = pageEncodingMatch.getJspProperty().getPageEncoding(); + } + if (deferedSyntaxAllowedAsLiteralMatch != null) { + isDeferedSyntaxAllowedAsLiteral = + deferedSyntaxAllowedAsLiteralMatch.getJspProperty().isDeferedSyntaxAllowedAsLiteral(); + } + if (trimDirectiveWhitespacesMatch != null) { + isTrimDirectiveWhitespaces = + trimDirectiveWhitespacesMatch.getJspProperty().isTrimDirectiveWhitespaces(); + } + + return new JspProperty(isXml, isELIgnored, isScriptingInvalid, + pageEncoding, includePreludes, includeCodas, + isDeferedSyntaxAllowedAsLiteral, isTrimDirectiveWhitespaces); + } + + /** + * To find out if an uri matches an url pattern in jsp config. If so, + * then the uri is a JSP page. This is used primarily for jspc. + */ + public boolean isJspPage(String uri) throws JasperException { + + init(); + if (jspProperties == null) { + return false; + } + + String uriPath = null; + int index = uri.lastIndexOf('/'); + if (index >=0 ) { + uriPath = uri.substring(0, index+1); + } + String uriExtension = null; + index = uri.lastIndexOf('.'); + if (index >=0) { + uriExtension = uri.substring(index+1); + } + + Iterator iter = jspProperties.iterator(); + while (iter.hasNext()) { + + JspPropertyGroup jpg = (JspPropertyGroup) iter.next(); + JspProperty jp = jpg.getJspProperty(); + + String extension = jpg.getExtension(); + String path = jpg.getPath(); + + if (extension == null) { + if (uri.equals(path)) { + // There is an exact match + return true; + } + } else { + if ((path == null || path.equals(uriPath)) && + (extension.equals("*") || extension.equals(uriExtension))) { + // Matches *, *.ext, /p/*, or /p/*.ext + return true; + } + } + } + return false; + } + + static class JspPropertyGroup { + private String path; + private String extension; + private JspProperty jspProperty; + + JspPropertyGroup(String path, String extension, + JspProperty jspProperty) { + this.path = path; + this.extension = extension; + this.jspProperty = jspProperty; + } + + public String getPath() { + return path; + } + + public String getExtension() { + return extension; + } + + public JspProperty getJspProperty() { + return jspProperty; + } + } + + static public class JspProperty { + + private String isXml; + private String elIgnored; + private String scriptingInvalid; + private String pageEncoding; + private Vector includePrelude; + private Vector includeCoda; + private String deferedSyntaxAllowedAsLiteral; + private String trimDirectiveWhitespaces; + + public JspProperty(String isXml, String elIgnored, + String scriptingInvalid, String pageEncoding, + Vector includePrelude, Vector includeCoda, + String deferedSyntaxAllowedAsLiteral, + String trimDirectiveWhitespaces) { + + this.isXml = isXml; + this.elIgnored = elIgnored; + this.scriptingInvalid = scriptingInvalid; + this.pageEncoding = pageEncoding; + this.includePrelude = includePrelude; + this.includeCoda = includeCoda; + this.deferedSyntaxAllowedAsLiteral = deferedSyntaxAllowedAsLiteral; + this.trimDirectiveWhitespaces = trimDirectiveWhitespaces; + } + + public String isXml() { + return isXml; + } + + public String isELIgnored() { + return elIgnored; + } + + public String isScriptingInvalid() { + return scriptingInvalid; + } + + public String getPageEncoding() { + return pageEncoding; + } + + public Vector getIncludePrelude() { + return includePrelude; + } + + public Vector getIncludeCoda() { + return includeCoda; + } + + public String isDeferedSyntaxAllowedAsLiteral() { + return deferedSyntaxAllowedAsLiteral; + } + + public String isTrimDirectiveWhitespaces() { + return trimDirectiveWhitespaces; + } + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspDocumentParser.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspDocumentParser.java new file mode 100644 index 000000000..3acd9d5b9 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspDocumentParser.java @@ -0,0 +1,1453 @@ +/* + * 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.CharArrayWriter; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import java.util.Iterator; +import java.util.List; +import java.util.jar.JarFile; + +import javax.servlet.jsp.tagext.TagFileInfo; +import javax.servlet.jsp.tagext.TagInfo; +import javax.servlet.jsp.tagext.TagLibraryInfo; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.apache.jasper.JasperException; +import org.apache.jasper.JspCompilationContext; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.LexicalHandler; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Class implementing a parser for a JSP document, that is, a JSP page in XML + * syntax. + * + * @author Jan Luehe + * @author Kin-man Chung + */ + +class JspDocumentParser + extends DefaultHandler + implements LexicalHandler, TagConstants { + + private static final String JSP_VERSION = "version"; + private static final String LEXICAL_HANDLER_PROPERTY = + "http://xml.org/sax/properties/lexical-handler"; + private static final String JSP_URI = "http://java.sun.com/JSP/Page"; + + private static final EnableDTDValidationException ENABLE_DTD_VALIDATION_EXCEPTION = + new EnableDTDValidationException( + "jsp.error.enable_dtd_validation", + null); + + private ParserController parserController; + private JspCompilationContext ctxt; + private PageInfo pageInfo; + private String path; + private StringBuffer charBuffer; + + // Node representing the XML element currently being parsed + private Node current; + + /* + * Outermost (in the nesting hierarchy) node whose body is declared to be + * scriptless. If a node's body is declared to be scriptless, all its + * nested nodes must be scriptless, too. + */ + private Node scriptlessBodyNode; + + private Locator locator; + + //Mark representing the start of the current element. Note + //that locator.getLineNumber() and locator.getColumnNumber() + //return the line and column numbers for the character + //immediately _following_ the current element. The underlying + //XMl parser eats white space that is not part of character + //data, so for Nodes that are not created from character data, + //this is the best we can do. But when we parse character data, + //we get an accurate starting location by starting with startMark + //as set by the previous element, and updating it as we advance + //through the characters. + private Mark startMark; + + // Flag indicating whether we are inside DTD declarations + private boolean inDTD; + + private boolean isValidating; + + private ErrorDispatcher err; + private boolean isTagFile; + private boolean directivesOnly; + private boolean isTop; + + // Nesting level of Tag dependent bodies + private int tagDependentNesting = 0; + // Flag set to delay incrmenting tagDependentNesting until jsp:body + // is first encountered + private boolean tagDependentPending = false; + + /* + * Constructor + */ + public JspDocumentParser( + ParserController pc, + String path, + boolean isTagFile, + boolean directivesOnly) { + this.parserController = pc; + this.ctxt = pc.getJspCompilationContext(); + this.pageInfo = pc.getCompiler().getPageInfo(); + this.err = pc.getCompiler().getErrorDispatcher(); + this.path = path; + this.isTagFile = isTagFile; + this.directivesOnly = directivesOnly; + this.isTop = true; + } + + /* + * Parses a JSP document by responding to SAX events. + * + * @throws JasperException + */ + public static Node.Nodes parse( + ParserController pc, + String path, + JarFile jarFile, + Node parent, + boolean isTagFile, + boolean directivesOnly, + String pageEnc, + String jspConfigPageEnc, + boolean isEncodingSpecifiedInProlog, + boolean isBomPresent) + throws JasperException { + + JspDocumentParser jspDocParser = + new JspDocumentParser(pc, path, isTagFile, directivesOnly); + Node.Nodes pageNodes = null; + + try { + + // Create dummy root and initialize it with given page encodings + Node.Root dummyRoot = new Node.Root(null, parent, true); + dummyRoot.setPageEncoding(pageEnc); + dummyRoot.setJspConfigPageEncoding(jspConfigPageEnc); + dummyRoot.setIsEncodingSpecifiedInProlog( + isEncodingSpecifiedInProlog); + dummyRoot.setIsBomPresent(isBomPresent); + jspDocParser.current = dummyRoot; + if (parent == null) { + jspDocParser.addInclude( + dummyRoot, + jspDocParser.pageInfo.getIncludePrelude()); + } else { + jspDocParser.isTop = false; + } + + // Parse the input + SAXParser saxParser = getSAXParser(false, jspDocParser); + InputStream inStream = null; + try { + inStream = JspUtil.getInputStream(path, jarFile, + jspDocParser.ctxt, + jspDocParser.err); + saxParser.parse(new InputSource(inStream), jspDocParser); + } catch (EnableDTDValidationException e) { + saxParser = getSAXParser(true, jspDocParser); + jspDocParser.isValidating = true; + if (inStream != null) { + try { + inStream.close(); + } catch (Exception any) { + } + } + inStream = JspUtil.getInputStream(path, jarFile, + jspDocParser.ctxt, + jspDocParser.err); + saxParser.parse(new InputSource(inStream), jspDocParser); + } finally { + if (inStream != null) { + try { + inStream.close(); + } catch (Exception any) { + } + } + } + + if (parent == null) { + jspDocParser.addInclude( + dummyRoot, + jspDocParser.pageInfo.getIncludeCoda()); + } + + // Create Node.Nodes from dummy root + pageNodes = new Node.Nodes(dummyRoot); + + } catch (IOException ioe) { + jspDocParser.err.jspError("jsp.error.data.file.read", path, ioe); + } catch (SAXParseException e) { + jspDocParser.err.jspError + (new Mark(jspDocParser.ctxt, path, e.getLineNumber(), + e.getColumnNumber()), + e.getMessage()); + } catch (Exception e) { + jspDocParser.err.jspError(e); + } + + return pageNodes; + } + + /* + * Processes the given list of included files. + * + * This is used to implement the include-prelude and include-coda + * subelements of the jsp-config element in web.xml + */ + private void addInclude(Node parent, List files) throws SAXException { + if (files != null) { + Iterator iter = files.iterator(); + while (iter.hasNext()) { + String file = (String)iter.next(); + AttributesImpl attrs = new AttributesImpl(); + attrs.addAttribute("", "file", "file", "CDATA", file); + + // Create a dummy Include directive node + Node includeDir = + new Node.IncludeDirective(attrs, null, // XXX + parent); + processIncludeDirective(file, includeDir); + } + } + } + + /* + * Receives notification of the start of an element. + * + * This method assigns the given tag attributes to one of 3 buckets: + * + * - "xmlns" attributes that represent (standard or custom) tag libraries. + * - "xmlns" attributes that do not represent tag libraries. + * - all remaining attributes. + * + * For each "xmlns" attribute that represents a custom tag library, the + * corresponding TagLibraryInfo object is added to the set of custom + * tag libraries. + */ + public void startElement( + String uri, + String localName, + String qName, + Attributes attrs) + throws SAXException { + + AttributesImpl taglibAttrs = null; + AttributesImpl nonTaglibAttrs = null; + AttributesImpl nonTaglibXmlnsAttrs = null; + + processChars(); + + checkPrefixes(uri, qName, attrs); + + if (directivesOnly && + !(JSP_URI.equals(uri) && localName.startsWith(DIRECTIVE_ACTION))) { + return; + } + + String currentPrefix = getPrefix(current.getQName()); + + // jsp:text must not have any subelements + if (JSP_URI.equals(uri) && TEXT_ACTION.equals(current.getLocalName()) + && "jsp".equals(currentPrefix)) { + throw new SAXParseException( + Localizer.getMessage("jsp.error.text.has_subelement"), + locator); + } + + startMark = new Mark(ctxt, path, locator.getLineNumber(), + locator.getColumnNumber()); + + if (attrs != null) { + /* + * Notice that due to a bug in the underlying SAX parser, the + * attributes must be enumerated in descending order. + */ + boolean isTaglib = false; + for (int i = attrs.getLength() - 1; i >= 0; i--) { + isTaglib = false; + String attrQName = attrs.getQName(i); + if (!attrQName.startsWith("xmlns")) { + if (nonTaglibAttrs == null) { + nonTaglibAttrs = new AttributesImpl(); + } + nonTaglibAttrs.addAttribute( + attrs.getURI(i), + attrs.getLocalName(i), + attrs.getQName(i), + attrs.getType(i), + attrs.getValue(i)); + } else { + if (attrQName.startsWith("xmlns:jsp")) { + isTaglib = true; + } else { + String attrUri = attrs.getValue(i); + // TaglibInfo for this uri already established in + // startPrefixMapping + isTaglib = pageInfo.hasTaglib(attrUri); + } + if (isTaglib) { + if (taglibAttrs == null) { + taglibAttrs = new AttributesImpl(); + } + taglibAttrs.addAttribute( + attrs.getURI(i), + attrs.getLocalName(i), + attrs.getQName(i), + attrs.getType(i), + attrs.getValue(i)); + } else { + if (nonTaglibXmlnsAttrs == null) { + nonTaglibXmlnsAttrs = new AttributesImpl(); + } + nonTaglibXmlnsAttrs.addAttribute( + attrs.getURI(i), + attrs.getLocalName(i), + attrs.getQName(i), + attrs.getType(i), + attrs.getValue(i)); + } + } + } + } + + Node node = null; + + if (tagDependentPending && JSP_URI.equals(uri) && + localName.equals(BODY_ACTION)) { + tagDependentPending = false; + tagDependentNesting++; + current = + parseStandardAction( + qName, + localName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + startMark, + current); + return; + } + + if (tagDependentPending && JSP_URI.equals(uri) && + localName.equals(ATTRIBUTE_ACTION)) { + current = + parseStandardAction( + qName, + localName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + startMark, + current); + return; + } + + if (tagDependentPending) { + tagDependentPending = false; + tagDependentNesting++; + } + + if (tagDependentNesting > 0) { + node = + new Node.UninterpretedTag( + qName, + localName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + startMark, + current); + } else if (JSP_URI.equals(uri)) { + node = + parseStandardAction( + qName, + localName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + startMark, + current); + } else { + node = + parseCustomAction( + qName, + localName, + uri, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + startMark, + current); + if (node == null) { + node = + new Node.UninterpretedTag( + qName, + localName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + startMark, + current); + } else { + // custom action + String bodyType = getBodyType((Node.CustomTag) node); + + if (scriptlessBodyNode == null + && bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) { + scriptlessBodyNode = node; + } + else if (TagInfo.BODY_CONTENT_TAG_DEPENDENT.equalsIgnoreCase(bodyType)) { + tagDependentPending = true; + } + } + } + + current = node; + } + + /* + * Receives notification of character data inside an element. + * + * The SAX does not call this method with all of the template text, but may + * invoke this method with chunks of it. This is a problem when we try + * to determine if the text contains only whitespaces, or when we are + * looking for an EL expression string. Therefore it is necessary to + * buffer and concatenate the chunks and process the concatenated text + * later (at beginTag and endTag) + * + * @param buf The characters + * @param offset The start position in the character array + * @param len The number of characters to use from the character array + * + * @throws SAXException + */ + public void characters(char[] buf, int offset, int len) { + + if (charBuffer == null) { + charBuffer = new StringBuffer(); + } + charBuffer.append(buf, offset, len); + } + + private void processChars() throws SAXException { + + if (charBuffer == null || directivesOnly) { + return; + } + + /* + * JSP.6.1.1: All textual nodes that have only white space are to be + * dropped from the document, except for nodes in a jsp:text element, + * and any leading and trailing white-space-only textual nodes in a + * jsp:attribute whose 'trim' attribute is set to FALSE, which are to + * be kept verbatim. + * JSP.6.2.3 defines white space characters. + */ + boolean isAllSpace = true; + if (!(current instanceof Node.JspText) + && !(current instanceof Node.NamedAttribute)) { + for (int i = 0; i < charBuffer.length(); i++) { + if (!(charBuffer.charAt(i) == ' ' + || charBuffer.charAt(i) == '\n' + || charBuffer.charAt(i) == '\r' + || charBuffer.charAt(i) == '\t')) { + isAllSpace = false; + break; + } + } + } + + if (!isAllSpace && tagDependentPending) { + tagDependentPending = false; + tagDependentNesting++; + } + + if (tagDependentNesting > 0) { + if (charBuffer.length() > 0) { + new Node.TemplateText(charBuffer.toString(), startMark, current); + } + startMark = new Mark(ctxt, path, locator.getLineNumber(), + locator.getColumnNumber()); + charBuffer = null; + return; + } + + if ((current instanceof Node.JspText) + || (current instanceof Node.NamedAttribute) + || !isAllSpace) { + + int line = startMark.getLineNumber(); + int column = startMark.getColumnNumber(); + + CharArrayWriter ttext = new CharArrayWriter(); + int lastCh = 0, elType = 0; + for (int i = 0; i < charBuffer.length(); i++) { + + int ch = charBuffer.charAt(i); + if (ch == '\n') { + column = 1; + line++; + } else { + column++; + } + if ((lastCh == '$' || lastCh == '#') && ch == '{') { + elType = lastCh; + if (ttext.size() > 0) { + new Node.TemplateText( + ttext.toString(), + startMark, + current); + ttext = new CharArrayWriter(); + //We subtract two from the column number to + //account for the '[$,#]{' that we've already parsed + startMark = new Mark(ctxt, path, line, column - 2); + } + // following "${" || "#{" to first unquoted "}" + i++; + boolean singleQ = false; + boolean doubleQ = false; + lastCh = 0; + for (;; i++) { + if (i >= charBuffer.length()) { + throw new SAXParseException( + Localizer.getMessage( + "jsp.error.unterminated", + (char) elType + "{"), + locator); + + } + ch = charBuffer.charAt(i); + if (ch == '\n') { + column = 1; + line++; + } else { + column++; + } + if (lastCh == '\\' && (singleQ || doubleQ)) { + ttext.write(ch); + lastCh = 0; + continue; + } + if (ch == '}') { + new Node.ELExpression((char) elType, + ttext.toString(), + startMark, + current); + ttext = new CharArrayWriter(); + startMark = new Mark(ctxt, path, line, column); + break; + } + if (ch == '"') + doubleQ = !doubleQ; + else if (ch == '\'') + singleQ = !singleQ; + + ttext.write(ch); + lastCh = ch; + } + } else if (lastCh == '\\' && (ch == '$' || ch == '#')) { + if (pageInfo.isELIgnored()) { + ttext.write('\\'); + } + ttext.write(ch); + ch = 0; // Not start of EL anymore + } else { + if (lastCh == '$' || lastCh == '#' || lastCh == '\\') { + ttext.write(lastCh); + } + if (ch != '$' && ch != '#' && ch != '\\') { + ttext.write(ch); + } + } + lastCh = ch; + } + if (lastCh == '$' || lastCh == '#' || lastCh == '\\') { + ttext.write(lastCh); + } + if (ttext.size() > 0) { + new Node.TemplateText(ttext.toString(), startMark, current); + } + } + startMark = new Mark(ctxt, path, locator.getLineNumber(), + locator.getColumnNumber()); + + charBuffer = null; + } + + /* + * Receives notification of the end of an element. + */ + public void endElement(String uri, String localName, String qName) + throws SAXException { + + processChars(); + + if (directivesOnly && + !(JSP_URI.equals(uri) && localName.startsWith(DIRECTIVE_ACTION))) { + return; + } + + if (current instanceof Node.NamedAttribute) { + boolean isTrim = ((Node.NamedAttribute)current).isTrim(); + Node.Nodes subElems = ((Node.NamedAttribute)current).getBody(); + for (int i = 0; subElems != null && i < subElems.size(); i++) { + Node subElem = subElems.getNode(i); + if (!(subElem instanceof Node.TemplateText)) { + continue; + } + // Ignore any whitespace (including spaces, carriage returns, + // line feeds, and tabs, that appear at the beginning and at + // the end of the body of the action, if the + // action's 'trim' attribute is set to TRUE (default). + // In addition, any textual nodes in the that + // have only white space are dropped from the document, with + // the exception of leading and trailing white-space-only + // textual nodes in a whose 'trim' attribute + // is set to FALSE, which must be kept verbatim. + if (i == 0) { + if (isTrim) { + ((Node.TemplateText)subElem).ltrim(); + } + } else if (i == subElems.size() - 1) { + if (isTrim) { + ((Node.TemplateText)subElem).rtrim(); + } + } else { + if (((Node.TemplateText)subElem).isAllSpace()) { + subElems.remove(subElem); + } + } + } + } else if (current instanceof Node.ScriptingElement) { + checkScriptingBody((Node.ScriptingElement)current); + } + + if ( isTagDependent(current)) { + tagDependentNesting--; + } + + if (scriptlessBodyNode != null + && current.equals(scriptlessBodyNode)) { + scriptlessBodyNode = null; + } + + if (current.getParent() != null) { + current = current.getParent(); + } + } + + /* + * Receives the document locator. + * + * @param locator the document locator + */ + public void setDocumentLocator(Locator locator) { + this.locator = locator; + } + + /* + * See org.xml.sax.ext.LexicalHandler. + */ + public void comment(char[] buf, int offset, int len) throws SAXException { + + processChars(); // Flush char buffer and remove white spaces + + // ignore comments in the DTD + if (!inDTD) { + startMark = + new Mark( + ctxt, + path, + locator.getLineNumber(), + locator.getColumnNumber()); + new Node.Comment(new String(buf, offset, len), startMark, current); + } + } + + /* + * See org.xml.sax.ext.LexicalHandler. + */ + public void startCDATA() throws SAXException { + + processChars(); // Flush char buffer and remove white spaces + startMark = new Mark(ctxt, path, locator.getLineNumber(), + locator.getColumnNumber()); + } + + /* + * See org.xml.sax.ext.LexicalHandler. + */ + public void endCDATA() throws SAXException { + processChars(); // Flush char buffer and remove white spaces + } + + /* + * See org.xml.sax.ext.LexicalHandler. + */ + public void startEntity(String name) throws SAXException { + // do nothing + } + + /* + * See org.xml.sax.ext.LexicalHandler. + */ + public void endEntity(String name) throws SAXException { + // do nothing + } + + /* + * See org.xml.sax.ext.LexicalHandler. + */ + public void startDTD(String name, String publicId, String systemId) + throws SAXException { + if (!isValidating) { + fatalError(ENABLE_DTD_VALIDATION_EXCEPTION); + } + + inDTD = true; + } + + /* + * See org.xml.sax.ext.LexicalHandler. + */ + public void endDTD() throws SAXException { + inDTD = false; + } + + /* + * Receives notification of a non-recoverable error. + */ + public void fatalError(SAXParseException e) throws SAXException { + throw e; + } + + /* + * Receives notification of a recoverable error. + */ + public void error(SAXParseException e) throws SAXException { + throw e; + } + + /* + * Receives notification of the start of a Namespace mapping. + */ + public void startPrefixMapping(String prefix, String uri) + throws SAXException { + TagLibraryInfo taglibInfo; + + if (directivesOnly && !(JSP_URI.equals(uri))) { + return; + } + + try { + taglibInfo = getTaglibInfo(prefix, uri); + } catch (JasperException je) { + throw new SAXParseException( + Localizer.getMessage("jsp.error.could.not.add.taglibraries"), + locator, + je); + } + + if (taglibInfo != null) { + if (pageInfo.getTaglib(uri) == null) { + pageInfo.addTaglib(uri, taglibInfo); + } + pageInfo.pushPrefixMapping(prefix, uri); + } else { + pageInfo.pushPrefixMapping(prefix, null); + } + } + + /* + * Receives notification of the end of a Namespace mapping. + */ + public void endPrefixMapping(String prefix) throws SAXException { + + if (directivesOnly) { + String uri = pageInfo.getURI(prefix); + if (!JSP_URI.equals(uri)) { + return; + } + } + + pageInfo.popPrefixMapping(prefix); + } + + //********************************************************************* + // Private utility methods + + private Node parseStandardAction( + String qName, + String localName, + Attributes nonTaglibAttrs, + Attributes nonTaglibXmlnsAttrs, + Attributes taglibAttrs, + Mark start, + Node parent) + throws SAXException { + + Node node = null; + + if (localName.equals(ROOT_ACTION)) { + if (!(current instanceof Node.Root)) { + throw new SAXParseException( + Localizer.getMessage("jsp.error.nested_jsproot"), + locator); + } + node = + new Node.JspRoot( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + if (isTop) { + pageInfo.setHasJspRoot(true); + } + } else if (localName.equals(PAGE_DIRECTIVE_ACTION)) { + if (isTagFile) { + throw new SAXParseException( + Localizer.getMessage( + "jsp.error.action.istagfile", + localName), + locator); + } + node = + new Node.PageDirective( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + String imports = nonTaglibAttrs.getValue("import"); + // There can only be one 'import' attribute per page directive + if (imports != null) { + ((Node.PageDirective)node).addImport(imports); + } + } else if (localName.equals(INCLUDE_DIRECTIVE_ACTION)) { + node = + new Node.IncludeDirective( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + processIncludeDirective(nonTaglibAttrs.getValue("file"), node); + } else if (localName.equals(DECLARATION_ACTION)) { + if (scriptlessBodyNode != null) { + // We're nested inside a node whose body is + // declared to be scriptless + throw new SAXParseException( + Localizer.getMessage( + "jsp.error.no.scriptlets", + localName), + locator); + } + node = + new Node.Declaration( + qName, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(SCRIPTLET_ACTION)) { + if (scriptlessBodyNode != null) { + // We're nested inside a node whose body is + // declared to be scriptless + throw new SAXParseException( + Localizer.getMessage( + "jsp.error.no.scriptlets", + localName), + locator); + } + node = + new Node.Scriptlet( + qName, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(EXPRESSION_ACTION)) { + if (scriptlessBodyNode != null) { + // We're nested inside a node whose body is + // declared to be scriptless + throw new SAXParseException( + Localizer.getMessage( + "jsp.error.no.scriptlets", + localName), + locator); + } + node = + new Node.Expression( + qName, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(USE_BEAN_ACTION)) { + node = + new Node.UseBean( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(SET_PROPERTY_ACTION)) { + node = + new Node.SetProperty( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(GET_PROPERTY_ACTION)) { + node = + new Node.GetProperty( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(INCLUDE_ACTION)) { + node = + new Node.IncludeAction( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(FORWARD_ACTION)) { + node = + new Node.ForwardAction( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(PARAM_ACTION)) { + node = + new Node.ParamAction( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(PARAMS_ACTION)) { + node = + new Node.ParamsAction( + qName, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(PLUGIN_ACTION)) { + node = + new Node.PlugIn( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(TEXT_ACTION)) { + node = + new Node.JspText( + qName, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(BODY_ACTION)) { + node = + new Node.JspBody( + qName, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(ATTRIBUTE_ACTION)) { + node = + new Node.NamedAttribute( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(OUTPUT_ACTION)) { + node = + new Node.JspOutput( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(TAG_DIRECTIVE_ACTION)) { + if (!isTagFile) { + throw new SAXParseException( + Localizer.getMessage( + "jsp.error.action.isnottagfile", + localName), + locator); + } + node = + new Node.TagDirective( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + String imports = nonTaglibAttrs.getValue("import"); + // There can only be one 'import' attribute per tag directive + if (imports != null) { + ((Node.TagDirective)node).addImport(imports); + } + } else if (localName.equals(ATTRIBUTE_DIRECTIVE_ACTION)) { + if (!isTagFile) { + throw new SAXParseException( + Localizer.getMessage( + "jsp.error.action.isnottagfile", + localName), + locator); + } + node = + new Node.AttributeDirective( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(VARIABLE_DIRECTIVE_ACTION)) { + if (!isTagFile) { + throw new SAXParseException( + Localizer.getMessage( + "jsp.error.action.isnottagfile", + localName), + locator); + } + node = + new Node.VariableDirective( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(INVOKE_ACTION)) { + if (!isTagFile) { + throw new SAXParseException( + Localizer.getMessage( + "jsp.error.action.isnottagfile", + localName), + locator); + } + node = + new Node.InvokeAction( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(DOBODY_ACTION)) { + if (!isTagFile) { + throw new SAXParseException( + Localizer.getMessage( + "jsp.error.action.isnottagfile", + localName), + locator); + } + node = + new Node.DoBodyAction( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(ELEMENT_ACTION)) { + node = + new Node.JspElement( + qName, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else if (localName.equals(FALLBACK_ACTION)) { + node = + new Node.FallBackAction( + qName, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + current); + } else { + throw new SAXParseException( + Localizer.getMessage( + "jsp.error.xml.badStandardAction", + localName), + locator); + } + + return node; + } + + /* + * Checks if the XML element with the given tag name is a custom action, + * and returns the corresponding Node object. + */ + private Node parseCustomAction( + String qName, + String localName, + String uri, + Attributes nonTaglibAttrs, + Attributes nonTaglibXmlnsAttrs, + Attributes taglibAttrs, + Mark start, + Node parent) + throws SAXException { + + // Check if this is a user-defined (custom) tag + TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri); + if (tagLibInfo == null) { + return null; + } + + TagInfo tagInfo = tagLibInfo.getTag(localName); + TagFileInfo tagFileInfo = tagLibInfo.getTagFile(localName); + if (tagInfo == null && tagFileInfo == null) { + throw new SAXException( + Localizer.getMessage("jsp.error.xml.bad_tag", localName, uri)); + } + Class tagHandlerClass = null; + if (tagInfo != null) { + String handlerClassName = tagInfo.getTagClassName(); + try { + tagHandlerClass = + ctxt.getClassLoader().loadClass(handlerClassName); + } catch (Exception e) { + throw new SAXException( + Localizer.getMessage("jsp.error.loadclass.taghandler", + handlerClassName, + qName), + e); + } + } + + String prefix = getPrefix(qName); + + Node.CustomTag ret = null; + if (tagInfo != null) { + ret = + new Node.CustomTag( + qName, + prefix, + localName, + uri, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + parent, + tagInfo, + tagHandlerClass); + } else { + ret = + new Node.CustomTag( + qName, + prefix, + localName, + uri, + nonTaglibAttrs, + nonTaglibXmlnsAttrs, + taglibAttrs, + start, + parent, + tagFileInfo); + } + + return ret; + } + + /* + * Creates the tag library associated with the given uri namespace, and + * returns it. + * + * @param prefix The prefix of the xmlns attribute + * @param uri The uri namespace (value of the xmlns attribute) + * + * @return The tag library associated with the given uri namespace + */ + private TagLibraryInfo getTaglibInfo(String prefix, String uri) + throws JasperException { + + TagLibraryInfo result = null; + + if (uri.startsWith(URN_JSPTAGDIR)) { + // uri (of the form "urn:jsptagdir:path") references tag file dir + String tagdir = uri.substring(URN_JSPTAGDIR.length()); + result = + new ImplicitTagLibraryInfo( + ctxt, + parserController, + pageInfo, + prefix, + tagdir, + err); + } else { + // uri references TLD file + boolean isPlainUri = false; + if (uri.startsWith(URN_JSPTLD)) { + // uri is of the form "urn:jsptld:path" + uri = uri.substring(URN_JSPTLD.length()); + } else { + isPlainUri = true; + } + + String[] location = ctxt.getTldLocation(uri); + if (location != null || !isPlainUri) { + if (ctxt.getOptions().isCaching()) { + result = (TagLibraryInfoImpl) ctxt.getOptions().getCache().get(uri); + } + if (result == null) { + /* + * If the uri value is a plain uri, a translation error must + * not be generated if the uri is not found in the taglib map. + * Instead, any actions in the namespace defined by the uri + * value must be treated as uninterpreted. + */ + result = + new TagLibraryInfoImpl( + ctxt, + parserController, + pageInfo, + prefix, + uri, + location, + err); + if (ctxt.getOptions().isCaching()) { + ctxt.getOptions().getCache().put(uri, result); + } + } + } + } + + return result; + } + + /* + * Ensures that the given body only contains nodes that are instances of + * TemplateText. + * + * This check is performed only for the body of a scripting (that is: + * declaration, scriptlet, or expression) element, after the end tag of a + * scripting element has been reached. + */ + private void checkScriptingBody(Node.ScriptingElement scriptingElem) + throws SAXException { + Node.Nodes body = scriptingElem.getBody(); + if (body != null) { + int size = body.size(); + for (int i = 0; i < size; i++) { + Node n = body.getNode(i); + if (!(n instanceof Node.TemplateText)) { + String elemType = SCRIPTLET_ACTION; + if (scriptingElem instanceof Node.Declaration) + elemType = DECLARATION_ACTION; + if (scriptingElem instanceof Node.Expression) + elemType = EXPRESSION_ACTION; + String msg = + Localizer.getMessage( + "jsp.error.parse.xml.scripting.invalid.body", + elemType); + throw new SAXException(msg); + } + } + } + } + + /* + * Parses the given file included via an include directive. + * + * @param fname The path to the included resource, as specified by the + * 'file' attribute of the include directive + * @param parent The Node representing the include directive + */ + private void processIncludeDirective(String fname, Node parent) + throws SAXException { + + if (fname == null) { + return; + } + + try { + parserController.parse(fname, parent, null); + } catch (FileNotFoundException fnfe) { + throw new SAXParseException( + Localizer.getMessage("jsp.error.file.not.found", fname), + locator, + fnfe); + } catch (Exception e) { + throw new SAXException(e); + } + } + + /* + * Checks an element's given URI, qname, and attributes to see if any + * of them hijack the 'jsp' prefix, that is, bind it to a namespace other + * than http://java.sun.com/JSP/Page. + * + * @param uri The element's URI + * @param qName The element's qname + * @param attrs The element's attributes + */ + private void checkPrefixes(String uri, String qName, Attributes attrs) { + + checkPrefix(uri, qName); + + int len = attrs.getLength(); + for (int i = 0; i < len; i++) { + checkPrefix(attrs.getURI(i), attrs.getQName(i)); + } + } + + /* + * Checks the given URI and qname to see if they hijack the 'jsp' prefix, + * which would be the case if qName contained the 'jsp' prefix and + * uri was different from http://java.sun.com/JSP/Page. + * + * @param uri The URI to check + * @param qName The qname to check + */ + private void checkPrefix(String uri, String qName) { + + String prefix = getPrefix(qName); + if (prefix.length() > 0) { + pageInfo.addPrefix(prefix); + if ("jsp".equals(prefix) && !JSP_URI.equals(uri)) { + pageInfo.setIsJspPrefixHijacked(true); + } + } + } + + private String getPrefix(String qName) { + int index = qName.indexOf(':'); + if (index != -1) { + return qName.substring(0, index); + } + return ""; + } + + /* + * Gets SAXParser. + * + * @param validating Indicates whether the requested SAXParser should + * be validating + * @param jspDocParser The JSP document parser + * + * @return The SAXParser + */ + private static SAXParser getSAXParser( + boolean validating, + JspDocumentParser jspDocParser) + throws Exception { + + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + + // Preserve xmlns attributes + factory.setFeature( + "http://xml.org/sax/features/namespace-prefixes", + true); + factory.setValidating(validating); + //factory.setFeature( + // "http://xml.org/sax/features/validation", + // validating); + + // Configure the parser + SAXParser saxParser = factory.newSAXParser(); + XMLReader xmlReader = saxParser.getXMLReader(); + xmlReader.setProperty(LEXICAL_HANDLER_PROPERTY, jspDocParser); + xmlReader.setErrorHandler(jspDocParser); + + return saxParser; + } + + /* + * Exception indicating that a DOCTYPE declaration is present, but + * validation is turned off. + */ + private static class EnableDTDValidationException + extends SAXParseException { + + EnableDTDValidationException(String message, Locator loc) { + super(message, loc); + } + } + + private static String getBodyType(Node.CustomTag custom) { + + if (custom.getTagInfo() != null) { + return custom.getTagInfo().getBodyContent(); + } + + return custom.getTagFileInfo().getTagInfo().getBodyContent(); + } + + private boolean isTagDependent(Node n) { + + if (n instanceof Node.CustomTag) { + String bodyType = getBodyType((Node.CustomTag) n); + return + TagInfo.BODY_CONTENT_TAG_DEPENDENT.equalsIgnoreCase(bodyType); + } + return false; + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspReader.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspReader.java new file mode 100644 index 000000000..4df569f91 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspReader.java @@ -0,0 +1,657 @@ +/* + * 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.CharArrayWriter; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; +import java.util.Vector; +import java.util.jar.JarFile; +import java.net.URL; +import java.net.MalformedURLException; + +import org.apache.jasper.JasperException; +import org.apache.jasper.JspCompilationContext; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +/** + * JspReader is an input buffer for the JSP parser. It should allow + * unlimited lookahead and pushback. It also has a bunch of parsing + * utility methods for understanding htmlesque thingies. + * + * @author Anil K. Vijendran + * @author Anselm Baird-Smith + * @author Harish Prabandham + * @author Rajiv Mordani + * @author Mandar Raje + * @author Danno Ferrin + * @author Kin-man Chung + * @author Shawn Bayern + * @author Mark Roth + */ + +class JspReader { + + /** + * Logger. + */ + private Log log = LogFactory.getLog(JspReader.class); + + /** + * The current spot in the file. + */ + private Mark current; + + /** + * What is this? + */ + private String master; + + /** + * The list of source files. + */ + private List sourceFiles; + + /** + * The current file ID (-1 indicates an error or no file). + */ + private int currFileId; + + /** + * Seems redundant. + */ + private int size; + + /** + * The compilation context. + */ + private JspCompilationContext context; + + /** + * The Jasper error dispatcher. + */ + private ErrorDispatcher err; + + /** + * Set to true when using the JspReader on a single file where we read up + * to the end and reset to the beginning many times. + * (as in ParserController.figureOutJspDocument()). + */ + private boolean singleFile; + + /** + * Constructor. + * + * @param ctxt The compilation context + * @param fname The file name + * @param encoding The file encoding + * @param jarFile ? + * @param err The error dispatcher + * @throws JasperException If a Jasper-internal error occurs + * @throws FileNotFoundException If the JSP file is not found (or is unreadable) + * @throws IOException If an IO-level error occurs, e.g. reading the file + */ + public JspReader(JspCompilationContext ctxt, + String fname, + String encoding, + JarFile jarFile, + ErrorDispatcher err) + throws JasperException, FileNotFoundException, IOException { + + this(ctxt, fname, encoding, + JspUtil.getReader(fname, encoding, jarFile, ctxt, err), + err); + } + + /** + * Constructor: same as above constructor but with initialized reader + * to the file given. + */ + public JspReader(JspCompilationContext ctxt, + String fname, + String encoding, + InputStreamReader reader, + ErrorDispatcher err) + throws JasperException, FileNotFoundException { + + this.context = ctxt; + this.err = err; + sourceFiles = new Vector(); + currFileId = 0; + size = 0; + singleFile = false; + pushFile(fname, encoding, reader); + } + + /** + * @return JSP compilation context with which this JspReader is + * associated + */ + JspCompilationContext getJspCompilationContext() { + return context; + } + + /** + * Returns the file at the given position in the list. + * + * @param fileid The file position in the list + * @return The file at that position, if found, null otherwise + */ + String getFile(final int fileid) { + return (String) sourceFiles.get(fileid); + } + + /** + * Checks if the current file has more input. + * + * @return True if more reading is possible + * @throws JasperException if an error occurs + */ + boolean hasMoreInput() throws JasperException { + if (current.cursor >= current.stream.length) { + if (singleFile) return false; + while (popFile()) { + if (current.cursor < current.stream.length) return true; + } + return false; + } + return true; + } + + int nextChar() throws JasperException { + if (!hasMoreInput()) + return -1; + + int ch = current.stream[current.cursor]; + + current.cursor++; + + if (ch == '\n') { + current.line++; + current.col = 0; + } else { + current.col++; + } + return ch; + } + + /** + * Back up the current cursor by one char, assumes current.cursor > 0, + * and that the char to be pushed back is not '\n'. + */ + void pushChar() { + current.cursor--; + current.col--; + } + + String getText(Mark start, Mark stop) throws JasperException { + Mark oldstart = mark(); + reset(start); + CharArrayWriter caw = new CharArrayWriter(); + while (!stop.equals(mark())) + caw.write(nextChar()); + caw.close(); + reset(oldstart); + return caw.toString(); + } + + int peekChar() throws JasperException { + if (!hasMoreInput()) + return -1; + return current.stream[current.cursor]; + } + + Mark mark() { + return new Mark(current); + } + + void reset(Mark mark) { + current = new Mark(mark); + } + + boolean matchesIgnoreCase(String string) throws JasperException { + Mark mark = mark(); + int ch = 0; + int i = 0; + do { + ch = nextChar(); + if (Character.toLowerCase((char) ch) != string.charAt(i++)) { + reset(mark); + return false; + } + } while (i < string.length()); + reset(mark); + return true; + } + + /** + * search the stream for a match to a string + * @param string The string to match + * @return true is one is found, the current position + * in stream is positioned after the search string, + * false otherwise, position in stream unchanged. + */ + boolean matches(String string) throws JasperException { + Mark mark = mark(); + int ch = 0; + int i = 0; + do { + ch = nextChar(); + if (((char) ch) != string.charAt(i++)) { + reset(mark); + return false; + } + } while (i < string.length()); + return true; + } + + boolean matchesETag(String tagName) throws JasperException { + Mark mark = mark(); + + if (!matches("') + return true; + + reset(mark); + return false; + } + + boolean matchesETagWithoutLessThan(String tagName) + throws JasperException + { + Mark mark = mark(); + + if (!matches("/" + tagName)) + return false; + skipSpaces(); + if (nextChar() == '>') + return true; + + reset(mark); + return false; + } + + + /** + * Looks ahead to see if there are optional spaces followed by + * the given String. If so, true is returned and those spaces and + * characters are skipped. If not, false is returned and the + * position is restored to where we were before. + */ + boolean matchesOptionalSpacesFollowedBy( String s ) + throws JasperException + { + Mark mark = mark(); + + skipSpaces(); + boolean result = matches( s ); + if( !result ) { + reset( mark ); + } + + return result; + } + + int skipSpaces() throws JasperException { + int i = 0; + while (hasMoreInput() && isSpace()) { + i++; + nextChar(); + } + return i; + } + + /** + * Skip until the given string is matched in the stream. + * When returned, the context is positioned past the end of the match. + * + * @param s The String to match. + * @return A non-null Mark instance (positioned immediately + * before the search string) if found, null + * otherwise. + */ + Mark skipUntil(String limit) throws JasperException { + Mark ret = null; + int limlen = limit.length(); + int ch; + + skip: + for (ret = mark(), ch = nextChar() ; ch != -1 ; + ret = mark(), ch = nextChar()) { + if (ch == limit.charAt(0)) { + Mark restart = mark(); + for (int i = 1 ; i < limlen ; i++) { + if (peekChar() == limit.charAt(i)) + nextChar(); + else { + reset(restart); + continue skip; + } + } + return ret; + } + } + return null; + } + + /** + * Skip until the given string is matched in the stream, but ignoring + * chars initially escaped by a '\'. + * When returned, the context is positioned past the end of the match. + * + * @param s The String to match. + * @return A non-null Mark instance (positioned immediately + * before the search string) if found, null + * otherwise. + */ + Mark skipUntilIgnoreEsc(String limit) throws JasperException { + Mark ret = null; + int limlen = limit.length(); + int ch; + int prev = 'x'; // Doesn't matter + + skip: + for (ret = mark(), ch = nextChar() ; ch != -1 ; + ret = mark(), prev = ch, ch = nextChar()) { + if (ch == '\\' && prev == '\\') { + ch = 0; // Double \ is not an escape char anymore + } + else if (ch == limit.charAt(0) && prev != '\\') { + for (int i = 1 ; i < limlen ; i++) { + if (peekChar() == limit.charAt(i)) + nextChar(); + else + continue skip; + } + return ret; + } + } + return null; + } + + /** + * Skip until the given end tag is matched in the stream. + * When returned, the context is positioned past the end of the tag. + * + * @param tag The name of the tag whose ETag () to match. + * @return A non-null Mark instance (positioned immediately + * before the ETag) if found, null otherwise. + */ + Mark skipUntilETag(String tag) throws JasperException { + Mark ret = skipUntil("') + ret = null; + } + return ret; + } + + final boolean isSpace() throws JasperException { + // Note: If this logic changes, also update Node.TemplateText.rtrim() + return peekChar() <= ' '; + } + + /** + * Parse a space delimited token. + * If quoted the token will consume all characters up to a matching quote, + * otherwise, it consumes up to the first delimiter character. + * + * @param quoted If true accept quoted strings. + */ + String parseToken(boolean quoted) throws JasperException { + StringBuffer stringBuffer = new StringBuffer(); + skipSpaces(); + stringBuffer.setLength(0); + + if (!hasMoreInput()) { + return ""; + } + + int ch = peekChar(); + + if (quoted) { + if (ch == '"' || ch == '\'') { + + char endQuote = ch == '"' ? '"' : '\''; + // Consume the open quote: + ch = nextChar(); + for (ch = nextChar(); ch != -1 && ch != endQuote; + ch = nextChar()) { + if (ch == '\\') + ch = nextChar(); + stringBuffer.append((char) ch); + } + // Check end of quote, skip closing quote: + if (ch == -1) { + err.jspError(mark(), "jsp.error.quotes.unterminated"); + } + } else { + err.jspError(mark(), "jsp.error.attr.quoted"); + } + } else { + if (!isDelimiter()) { + // Read value until delimiter is found: + do { + ch = nextChar(); + // Take care of the quoting here. + if (ch == '\\') { + if (peekChar() == '"' || peekChar() == '\'' || + peekChar() == '>' || peekChar() == '%') + ch = nextChar(); + } + stringBuffer.append((char) ch); + } while (!isDelimiter()); + } + } + + return stringBuffer.toString(); + } + + void setSingleFile(boolean val) { + singleFile = val; + } + + + /** + * Gets the URL for the given path name. + * + * @param path Path name + * + * @return URL for the given path name. + * + * @exception MalformedURLException if the path name is not given in + * the correct form + */ + URL getResource(String path) throws MalformedURLException { + return context.getResource(path); + } + + + /** + * Parse utils - Is current character a token delimiter ? + * Delimiters are currently defined to be =, >, <, ", and ' or any + * any space character as defined by isSpace. + * + * @return A boolean. + */ + private boolean isDelimiter() throws JasperException { + if (! isSpace()) { + int ch = peekChar(); + // Look for a single-char work delimiter: + if (ch == '=' || ch == '>' || ch == '"' || ch == '\'' + || ch == '/') { + return true; + } + // Look for an end-of-comment or end-of-tag: + if (ch == '-') { + Mark mark = mark(); + if (((ch = nextChar()) == '>') + || ((ch == '-') && (nextChar() == '>'))) { + reset(mark); + return true; + } else { + reset(mark); + return false; + } + } + return false; + } else { + return true; + } + } + + /** + * Register a new source file. + * This method is used to implement file inclusion. Each included file + * gets a unique identifier (which is the index in the array of source + * files). + * + * @return The index of the now registered file. + */ + private int registerSourceFile(final String file) { + if (sourceFiles.contains(file)) { + return -1; + } + + sourceFiles.add(file); + this.size++; + + return sourceFiles.size() - 1; + } + + + /** + * Unregister the source file. + * This method is used to implement file inclusion. Each included file + * gets a uniq identifier (which is the index in the array of source + * files). + * + * @return The index of the now registered file. + */ + private int unregisterSourceFile(final String file) { + if (!sourceFiles.contains(file)) { + return -1; + } + + sourceFiles.remove(file); + this.size--; + return sourceFiles.size() - 1; + } + + /** + * Push a file (and its associated Stream) on the file stack. THe + * current position in the current file is remembered. + */ + private void pushFile(String file, String encoding, + InputStreamReader reader) + throws JasperException, FileNotFoundException { + + // Register the file + String longName = file; + + int fileid = registerSourceFile(longName); + + if (fileid == -1) { + // Bugzilla 37407: http://issues.apache.org/bugzilla/show_bug.cgi?id=37407 + if(reader != null) { + try { + reader.close(); + } catch (Exception any) { + if(log.isDebugEnabled()) { + log.debug("Exception closing reader: ", any); + } + } + } + + err.jspError("jsp.error.file.already.registered", file); + } + + currFileId = fileid; + + try { + CharArrayWriter caw = new CharArrayWriter(); + char buf[] = new char[1024]; + for (int i = 0 ; (i = reader.read(buf)) != -1 ;) + caw.write(buf, 0, i); + caw.close(); + if (current == null) { + current = new Mark(this, caw.toCharArray(), fileid, + getFile(fileid), master, encoding); + } else { + current.pushStream(caw.toCharArray(), fileid, getFile(fileid), + longName, encoding); + } + } catch (Throwable ex) { + log.error("Exception parsing file ", ex); + // Pop state being constructed: + popFile(); + err.jspError("jsp.error.file.cannot.read", file); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (Exception any) { + if(log.isDebugEnabled()) { + log.debug("Exception closing reader: ", any); + } + } + } + } + } + + /** + * Pop a file from the file stack. The field "current" is retored + * to the value to point to the previous files, if any, and is set + * to null otherwise. + * @return true is there is a previous file on the stack. + * false otherwise. + */ + private boolean popFile() throws JasperException { + + // Is stack created ? (will happen if the Jsp file we're looking at is + // missing. + if (current == null || currFileId < 0) { + return false; + } + + // Restore parser state: + String fName = getFile(currFileId); + currFileId = unregisterSourceFile(fName); + if (currFileId < -1) { + err.jspError("jsp.error.file.not.registered", fName); + } + + Mark previous = current.popStream(); + if (previous != null) { + master = current.baseDir; + current = previous; + return true; + } + // Note that although the current file is undefined here, "current" + // is not set to null just for convience, for it maybe used to + // set the current (undefined) position. + return false; + } +} + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java new file mode 100644 index 000000000..0e47c0abf --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java @@ -0,0 +1,446 @@ +/* + * 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.File; +import java.io.FileNotFoundException; +import java.io.FilePermission; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.CodeSource; +import java.security.PermissionCollection; +import java.security.Policy; +import java.security.cert.Certificate; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.servlet.ServletContext; +import javax.servlet.jsp.JspFactory; + +import org.apache.jasper.Constants; +import org.apache.jasper.JspCompilationContext; +import org.apache.jasper.Options; +import org.apache.jasper.runtime.JspFactoryImpl; +import org.apache.jasper.security.SecurityClassLoad; +import org.apache.jasper.servlet.JspServletWrapper; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +/** + * Class for tracking JSP compile time file dependencies when the + * &060;%@include file="..."%&062; directive is used. + * + * A background thread periodically checks the files a JSP page + * is dependent upon. If a dpendent file changes the JSP page + * which included it is recompiled. + * + * Only used if a web application context is a directory. + * + * @author Glenn L. Nielsen + * @version $Revision: 505593 $ + */ +public final class JspRuntimeContext { + + // Logger + private Log log = LogFactory.getLog(JspRuntimeContext.class); + + /* + * Counts how many times the webapp's JSPs have been reloaded. + */ + private int jspReloadCount; + + /** + * Preload classes required at runtime by a JSP servlet so that + * we don't get a defineClassInPackage security exception. + */ + static { + JspFactoryImpl factory = new JspFactoryImpl(); + SecurityClassLoad.securityClassLoad(factory.getClass().getClassLoader()); + if( System.getSecurityManager() != null ) { + String basePackage = "org.apache.jasper."; + try { + factory.getClass().getClassLoader().loadClass( basePackage + + "runtime.JspFactoryImpl$PrivilegedGetPageContext"); + factory.getClass().getClassLoader().loadClass( basePackage + + "runtime.JspFactoryImpl$PrivilegedReleasePageContext"); + factory.getClass().getClassLoader().loadClass( basePackage + + "runtime.JspRuntimeLibrary"); + factory.getClass().getClassLoader().loadClass( basePackage + + "runtime.JspRuntimeLibrary$PrivilegedIntrospectHelper"); + factory.getClass().getClassLoader().loadClass( basePackage + + "runtime.ServletResponseWrapperInclude"); + factory.getClass().getClassLoader().loadClass( basePackage + + "servlet.JspServletWrapper"); + } catch (ClassNotFoundException ex) { + throw new IllegalStateException(ex); + } + } + + JspFactory.setDefaultFactory(factory); + } + + // ----------------------------------------------------------- Constructors + + /** + * Create a JspRuntimeContext for a web application context. + * + * Loads in any previously generated dependencies from file. + * + * @param context ServletContext for web application + */ + public JspRuntimeContext(ServletContext context, Options options) { + + this.context = context; + this.options = options; + + // Get the parent class loader + parentClassLoader = + (URLClassLoader) Thread.currentThread().getContextClassLoader(); + if (parentClassLoader == null) { + parentClassLoader = + (URLClassLoader)this.getClass().getClassLoader(); + } + + if (log.isDebugEnabled()) { + if (parentClassLoader != null) { + log.debug(Localizer.getMessage("jsp.message.parent_class_loader_is", + parentClassLoader.toString())); + } else { + log.debug(Localizer.getMessage("jsp.message.parent_class_loader_is", + "")); + } + } + + initClassPath(); + + if (context instanceof org.apache.jasper.servlet.JspCServletContext) { + return; + } + + if (Constants.IS_SECURITY_ENABLED) { + initSecurity(); + } + + // If this web application context is running from a + // directory, start the background compilation thread + String appBase = context.getRealPath("/"); + if (!options.getDevelopment() + && appBase != null + && options.getCheckInterval() > 0) { + lastCheck = System.currentTimeMillis(); + } + } + + // ----------------------------------------------------- Instance Variables + + /** + * This web applications ServletContext + */ + private ServletContext context; + private Options options; + private URLClassLoader parentClassLoader; + private PermissionCollection permissionCollection; + private CodeSource codeSource; + private String classpath; + private long lastCheck = -1L; + + /** + * Maps JSP pages to their JspServletWrapper's + */ + private Map jsps = new ConcurrentHashMap(); + + + // ------------------------------------------------------ Public Methods + + /** + * Add a new JspServletWrapper. + * + * @param jspUri JSP URI + * @param jsw Servlet wrapper for JSP + */ + public void addWrapper(String jspUri, JspServletWrapper jsw) { + jsps.put(jspUri, jsw); + } + + /** + * Get an already existing JspServletWrapper. + * + * @param jspUri JSP URI + * @return JspServletWrapper for JSP + */ + public JspServletWrapper getWrapper(String jspUri) { + return jsps.get(jspUri); + } + + /** + * Remove a JspServletWrapper. + * + * @param jspUri JSP URI of JspServletWrapper to remove + */ + public void removeWrapper(String jspUri) { + jsps.remove(jspUri); + } + + /** + * Returns the number of JSPs for which JspServletWrappers exist, i.e., + * the number of JSPs that have been loaded into the webapp. + * + * @return The number of JSPs that have been loaded into the webapp + */ + public int getJspCount() { + return jsps.size(); + } + + /** + * Get the SecurityManager Policy CodeSource for this web + * applicaiton context. + * + * @return CodeSource for JSP + */ + public CodeSource getCodeSource() { + return codeSource; + } + + /** + * Get the parent URLClassLoader. + * + * @return URLClassLoader parent + */ + public URLClassLoader getParentClassLoader() { + return parentClassLoader; + } + + /** + * Get the SecurityManager PermissionCollection for this + * web application context. + * + * @return PermissionCollection permissions + */ + public PermissionCollection getPermissionCollection() { + return permissionCollection; + } + + /** + * Process a "destory" event for this web application context. + */ + public void destroy() { + Iterator servlets = jsps.values().iterator(); + while (servlets.hasNext()) { + ((JspServletWrapper) servlets.next()).destroy(); + } + } + + /** + * Increments the JSP reload counter. + */ + public synchronized void incrementJspReloadCount() { + jspReloadCount++; + } + + /** + * Resets the JSP reload counter. + * + * @param count Value to which to reset the JSP reload counter + */ + public synchronized void setJspReloadCount(int count) { + this.jspReloadCount = count; + } + + /** + * Gets the current value of the JSP reload counter. + * + * @return The current value of the JSP reload counter + */ + public int getJspReloadCount() { + return jspReloadCount; + } + + + /** + * Method used by background thread to check the JSP dependencies + * registered with this class for JSP's. + */ + public void checkCompile() { + + if (lastCheck < 0) { + // Checking was disabled + return; + } + long now = System.currentTimeMillis(); + if (now > (lastCheck + (options.getCheckInterval() * 1000L))) { + lastCheck = now; + } else { + return; + } + + Object [] wrappers = jsps.values().toArray(); + for (int i = 0; i < wrappers.length; i++ ) { + JspServletWrapper jsw = (JspServletWrapper)wrappers[i]; + JspCompilationContext ctxt = jsw.getJspEngineContext(); + // JspServletWrapper also synchronizes on this when + // it detects it has to do a reload + synchronized(jsw) { + try { + ctxt.compile(); + } catch (FileNotFoundException ex) { + ctxt.incrementRemoved(); + } catch (Throwable t) { + jsw.getServletContext().log("Background compile failed", + t); + } + } + } + + } + + /** + * The classpath that is passed off to the Java compiler. + */ + public String getClassPath() { + return classpath; + } + + + // -------------------------------------------------------- Private Methods + + + /** + * Method used to initialize classpath for compiles. + */ + private void initClassPath() { + + URL [] urls = parentClassLoader.getURLs(); + StringBuffer cpath = new StringBuffer(); + String sep = System.getProperty("path.separator"); + + for(int i = 0; i < urls.length; i++) { + // Tomcat 4 can use URL's other than file URL's, + // a protocol other than file: will generate a + // bad file system path, so only add file: + // protocol URL's to the classpath. + + if( urls[i].getProtocol().equals("file") ) { + cpath.append((String)urls[i].getFile()+sep); + } + } + + cpath.append(options.getScratchDir() + sep); + + String cp = (String) context.getAttribute(Constants.SERVLET_CLASSPATH); + if (cp == null || cp.equals("")) { + cp = options.getClassPath(); + } + + classpath = cpath.toString() + cp; + + if(log.isDebugEnabled()) { + log.debug("Compilation classpath initialized: " + getClassPath()); + } + } + + /** + * Method used to initialize SecurityManager data. + */ + private void initSecurity() { + + // Setup the PermissionCollection for this web app context + // based on the permissions configured for the root of the + // web app context directory, then add a file read permission + // for that directory. + Policy policy = Policy.getPolicy(); + if( policy != null ) { + try { + // Get the permissions for the web app context + String docBase = context.getRealPath("/"); + if( docBase == null ) { + docBase = options.getScratchDir().toString(); + } + String codeBase = docBase; + if (!codeBase.endsWith(File.separator)){ + codeBase = codeBase + File.separator; + } + File contextDir = new File(codeBase); + URL url = contextDir.getCanonicalFile().toURL(); + codeSource = new CodeSource(url,(Certificate[])null); + permissionCollection = policy.getPermissions(codeSource); + + // Create a file read permission for web app context directory + if (!docBase.endsWith(File.separator)){ + permissionCollection.add + (new FilePermission(docBase,"read")); + docBase = docBase + File.separator; + } else { + permissionCollection.add + (new FilePermission + (docBase.substring(0,docBase.length() - 1),"read")); + } + docBase = docBase + "-"; + permissionCollection.add(new FilePermission(docBase,"read")); + + // Create a file read permission for web app tempdir (work) + // directory + String workDir = options.getScratchDir().toString(); + if (!workDir.endsWith(File.separator)){ + permissionCollection.add + (new FilePermission(workDir,"read")); + workDir = workDir + File.separator; + } + workDir = workDir + "-"; + permissionCollection.add(new FilePermission(workDir,"read")); + + // Allow the JSP to access org.apache.jasper.runtime.HttpJspBase + permissionCollection.add( new RuntimePermission( + "accessClassInPackage.org.apache.jasper.runtime") ); + + if (parentClassLoader instanceof URLClassLoader) { + URL [] urls = parentClassLoader.getURLs(); + String jarUrl = null; + String jndiUrl = null; + for (int i=0; i') { + caw.write('%'); + caw.write('>'); + i = i + 2; + } else { + caw.write(chars[i]); + } + } + return caw.toCharArray(); + } + + public static char[] escapeQuotes (char []chars) { + // Prescan to convert %\> to %> + String s = new String(chars); + while (true) { + int n = s.indexOf("%\\>"); + if (n < 0) + break; + StringBuffer sb = new StringBuffer(s.substring(0, n)); + sb.append("%>"); + sb.append(s.substring(n + 3)); + s = sb.toString(); + } + chars = s.toCharArray(); + return (chars); + + + // Escape all backslashes not inside a Java string literal + /* + CharArrayWriter caw = new CharArrayWriter(); + boolean inJavaString = false; + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '"') inJavaString = !inJavaString; + // escape out the escape character + if (!inJavaString && (chars[i] == '\\')) caw.write('\\'); + caw.write(chars[i]); + } + return caw.toCharArray(); + */ + } + + /** + * Checks if the token is a runtime expression. + * In standard JSP syntax, a runtime expression starts with '<%' and + * ends with '%>'. When the JSP document is in XML syntax, a runtime + * expression starts with '%=' and ends with '%'. + * + * @param token The token to be checked + * return whether the token is a runtime expression or not. + */ + public static boolean isExpression(String token, boolean isXml) { + String openExpr; + String closeExpr; + if (isXml) { + openExpr = OPEN_EXPR_XML; + closeExpr = CLOSE_EXPR_XML; + } else { + openExpr = OPEN_EXPR; + closeExpr = CLOSE_EXPR; + } + if (token.startsWith(openExpr) && token.endsWith(closeExpr)) { + return true; + } else { + return false; + } + } + + /** + * @return the "expression" part of a runtime expression, + * taking the delimiters out. + */ + public static String getExpr (String expression, boolean isXml) { + String returnString; + String openExpr; + String closeExpr; + if (isXml) { + openExpr = OPEN_EXPR_XML; + closeExpr = CLOSE_EXPR_XML; + } else { + openExpr = OPEN_EXPR; + closeExpr = CLOSE_EXPR; + } + int length = expression.length(); + if (expression.startsWith(openExpr) && + expression.endsWith(closeExpr)) { + returnString = expression.substring( + openExpr.length(), length - closeExpr.length()); + } else { + returnString = ""; + } + return returnString; + } + + /** + * Takes a potential expression and converts it into XML form + */ + public static String getExprInXml(String expression) { + String returnString; + int length = expression.length(); + + if (expression.startsWith(OPEN_EXPR) + && expression.endsWith(CLOSE_EXPR)) { + returnString = expression.substring (1, length - 1); + } else { + returnString = expression; + } + + return escapeXml(returnString.replace(Constants.ESC, '$')); + } + + /** + * Checks to see if the given scope is valid. + * + * @param scope The scope to be checked + * @param n The Node containing the 'scope' attribute whose value is to be + * checked + * @param err error dispatcher + * + * @throws JasperException if scope is not null and different from + * "page", "request", "session", and + * "application" + */ + public static void checkScope(String scope, Node n, ErrorDispatcher err) + throws JasperException { + if (scope != null && !scope.equals("page") && !scope.equals("request") + && !scope.equals("session") && !scope.equals("application")) { + err.jspError(n, "jsp.error.invalid.scope", scope); + } + } + + /** + * Checks if all mandatory attributes are present and if all attributes + * present have valid names. Checks attributes specified as XML-style + * attributes as well as attributes specified using the jsp:attribute + * standard action. + */ + public static void checkAttributes(String typeOfTag, + Node n, + ValidAttribute[] validAttributes, + ErrorDispatcher err) + throws JasperException { + Attributes attrs = n.getAttributes(); + Mark start = n.getStart(); + boolean valid = true; + + // AttributesImpl.removeAttribute is broken, so we do this... + int tempLength = (attrs == null) ? 0 : attrs.getLength(); + Vector temp = new Vector(tempLength, 1); + for (int i = 0; i < tempLength; i++) { + String qName = attrs.getQName(i); + if ((!qName.equals("xmlns")) && (!qName.startsWith("xmlns:"))) + temp.addElement(qName); + } + + // Add names of attributes specified using jsp:attribute + Node.Nodes tagBody = n.getBody(); + if( tagBody != null ) { + int numSubElements = tagBody.size(); + for( int i = 0; i < numSubElements; i++ ) { + Node node = tagBody.getNode( i ); + if( node instanceof Node.NamedAttribute ) { + String attrName = node.getAttributeValue( "name" ); + temp.addElement( attrName ); + // Check if this value appear in the attribute of the node + if (n.getAttributeValue(attrName) != null) { + err.jspError(n, "jsp.error.duplicate.name.jspattribute", + attrName); + } + } + else { + // Nothing can come before jsp:attribute, and only + // jsp:body can come after it. + break; + } + } + } + + /* + * First check to see if all the mandatory attributes are present. + * If so only then proceed to see if the other attributes are valid + * for the particular tag. + */ + String missingAttribute = null; + + for (int i = 0; i < validAttributes.length; i++) { + int attrPos; + if (validAttributes[i].mandatory) { + attrPos = temp.indexOf(validAttributes[i].name); + if (attrPos != -1) { + temp.remove(attrPos); + valid = true; + } else { + valid = false; + missingAttribute = validAttributes[i].name; + break; + } + } + } + + // If mandatory attribute is missing then the exception is thrown + if (!valid) + err.jspError(start, "jsp.error.mandatory.attribute", typeOfTag, + missingAttribute); + + // Check to see if there are any more attributes for the specified tag. + int attrLeftLength = temp.size(); + if (attrLeftLength == 0) + return; + + // Now check to see if the rest of the attributes are valid too. + String attribute = null; + + for (int j = 0; j < attrLeftLength; j++) { + valid = false; + attribute = (String) temp.elementAt(j); + for (int i = 0; i < validAttributes.length; i++) { + if (attribute.equals(validAttributes[i].name)) { + valid = true; + break; + } + } + if (!valid) + err.jspError(start, "jsp.error.invalid.attribute", typeOfTag, + attribute); + } + // XXX *could* move EL-syntax validation here... (sb) + } + + public static String escapeQueryString(String unescString) { + if ( unescString == null ) + return null; + + String escString = ""; + String shellSpChars = "\\\""; + + for(int index=0; index') { + sb.append(">"); + } else if (c == '\'') { + sb.append("'"); + } else if (c == '&') { + sb.append("&"); + } else if (c == '"') { + sb.append("""); + } else { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Replaces any occurrences of the character replace with the + * string with. + */ + public static String replace(String name, char replace, String with) { + StringBuffer buf = new StringBuffer(); + int begin = 0; + int end; + int last = name.length(); + + while (true) { + end = name.indexOf(replace, begin); + if (end < 0) { + end = last; + } + buf.append(name.substring(begin, end)); + if (end == last) { + break; + } + buf.append(with); + begin = end + 1; + } + + return buf.toString(); + } + + public static class ValidAttribute { + String name; + boolean mandatory; + boolean rtexprvalue; // not used now + + public ValidAttribute (String name, boolean mandatory, + boolean rtexprvalue ) + { + this.name = name; + this.mandatory = mandatory; + this.rtexprvalue = rtexprvalue; + } + + public ValidAttribute (String name, boolean mandatory) { + this( name, mandatory, false ); + } + + public ValidAttribute (String name) { + this (name, false); + } + } + + /** + * Convert a String value to 'boolean'. + * Besides the standard conversions done by + * Boolean.valueOf(s).booleanValue(), the value "yes" + * (ignore case) is also converted to 'true'. + * If 's' is null, then 'false' is returned. + * + * @param s the string to be converted + * @return the boolean value associated with the string s + */ + public static boolean booleanValue(String s) { + boolean b = false; + if (s != null) { + if (s.equalsIgnoreCase("yes")) { + b = true; + } else { + b = Boolean.valueOf(s).booleanValue(); + } + } + return b; + } + + /** + * Returns the Class object associated with the class or + * interface with the given string name. + * + *

The Class object is determined by passing the given string + * name to the Class.forName() method, unless the given string + * name represents a primitive type, in which case it is converted to a + * Class object by appending ".class" to it (e.g., "int.class"). + */ + public static Class toClass(String type, ClassLoader loader) + throws ClassNotFoundException { + + Class c = null; + int i0 = type.indexOf('['); + int dims = 0; + if (i0 > 0) { + // This is an array. Count the dimensions + for (int i = 0; i < type.length(); i++) { + if (type.charAt(i) == '[') + dims++; + } + type = type.substring(0, i0); + } + + if ("boolean".equals(type)) + c = boolean.class; + else if ("char".equals(type)) + c = char.class; + else if ("byte".equals(type)) + c = byte.class; + else if ("short".equals(type)) + c = short.class; + else if ("int".equals(type)) + c = int.class; + else if ("long".equals(type)) + c = long.class; + else if ("float".equals(type)) + c = float.class; + else if ("double".equals(type)) + c = double.class; + else if (type.indexOf('[') < 0) + c = loader.loadClass(type); + + if (dims == 0) + return c; + + if (dims == 1) + return java.lang.reflect.Array.newInstance(c, 1).getClass(); + + // Array of more than i dimension + return java.lang.reflect.Array.newInstance(c, new int[dims]).getClass(); + } + + /** + * Produces a String representing a call to the EL interpreter. + * @param expression a String containing zero or more "${}" expressions + * @param expectedType the expected type of the interpreted result + * @param fnmapvar Variable pointing to a function map. + * @param XmlEscape True if the result should do XML escaping + * @return a String representing a call to the EL interpreter. + */ + public static String interpreterCall(boolean isTagFile, + String expression, + Class expectedType, + String fnmapvar, + boolean XmlEscape ) + { + /* + * Determine which context object to use. + */ + String jspCtxt = null; + if (isTagFile) + jspCtxt = "this.getJspContext()"; + else + jspCtxt = "_jspx_page_context"; + + /* + * Determine whether to use the expected type's textual name + * or, if it's a primitive, the name of its correspondent boxed + * type. + */ + String targetType = expectedType.getName(); + String primitiveConverterMethod = null; + if (expectedType.isPrimitive()) { + if (expectedType.equals(Boolean.TYPE)) { + targetType = Boolean.class.getName(); + primitiveConverterMethod = "booleanValue"; + } else if (expectedType.equals(Byte.TYPE)) { + targetType = Byte.class.getName(); + primitiveConverterMethod = "byteValue"; + } else if (expectedType.equals(Character.TYPE)) { + targetType = Character.class.getName(); + primitiveConverterMethod = "charValue"; + } else if (expectedType.equals(Short.TYPE)) { + targetType = Short.class.getName(); + primitiveConverterMethod = "shortValue"; + } else if (expectedType.equals(Integer.TYPE)) { + targetType = Integer.class.getName(); + primitiveConverterMethod = "intValue"; + } else if (expectedType.equals(Long.TYPE)) { + targetType = Long.class.getName(); + primitiveConverterMethod = "longValue"; + } else if (expectedType.equals(Float.TYPE)) { + targetType = Float.class.getName(); + primitiveConverterMethod = "floatValue"; + } else if (expectedType.equals(Double.TYPE)) { + targetType = Double.class.getName(); + primitiveConverterMethod = "doubleValue"; + } + } + + if (primitiveConverterMethod != null) { + XmlEscape = false; + } + + /* + * Build up the base call to the interpreter. + */ + // XXX - We use a proprietary call to the interpreter for now + // as the current standard machinery is inefficient and requires + // lots of wrappers and adapters. This should all clear up once + // the EL interpreter moves out of JSTL and into its own project. + // In the future, this should be replaced by code that calls + // ExpressionEvaluator.parseExpression() and then cache the resulting + // expression objects. The interpreterCall would simply select + // one of the pre-cached expressions and evaluate it. + // Note that PageContextImpl implements VariableResolver and + // the generated Servlet/SimpleTag implements FunctionMapper, so + // that machinery is already in place (mroth). + targetType = toJavaSourceType(targetType); + StringBuffer call = new StringBuffer( + "(" + targetType + ") " + + "org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate" + + "(" + Generator.quote(expression) + ", " + + targetType + ".class, " + + "(PageContext)" + jspCtxt + + ", " + fnmapvar + + ", " + XmlEscape + + ")"); + + /* + * Add the primitive converter method if we need to. + */ + if (primitiveConverterMethod != null) { + call.insert(0, "("); + call.append(")." + primitiveConverterMethod + "()"); + } + + return call.toString(); + } + + /** + * Validates the syntax of all ${} expressions within the given string. + * @param where the approximate location of the expressions in the JSP page + * @param expressions a string containing zero or more "${}" expressions + * @param err an error dispatcher to use + * @deprecated now delegated to the org.apache.el Package + */ + public static void validateExpressions(Mark where, + String expressions, + Class expectedType, + FunctionMapper functionMapper, + ErrorDispatcher err) + throws JasperException { + +// try { +// +// JspUtil.expressionEvaluator.parseExpression( expressions, +// expectedType, functionMapper ); +// } +// catch( ELParseException e ) { +// err.jspError(where, "jsp.error.invalid.expression", expressions, +// e.toString() ); +// } +// catch( ELException e ) { +// err.jspError(where, "jsp.error.invalid.expression", expressions, +// e.toString() ); +// } + } + + /** + * Resets the temporary variable name. + * (not thread-safe) + * @deprecated + */ + public static void resetTemporaryVariableName() { + tempSequenceNumber = 0; + } + + /** + * Generates a new temporary variable name. + * (not thread-safe) + * @deprecated + */ + public static String nextTemporaryVariableName() { + return Constants.TEMP_VARIABLE_NAME_PREFIX + (tempSequenceNumber++); + } + + public static String coerceToPrimitiveBoolean(String s, + boolean isNamedAttribute) { + if (isNamedAttribute) { + return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToBoolean(" + s + ")"; + } else { + if (s == null || s.length() == 0) + return "false"; + else + return Boolean.valueOf(s).toString(); + } + } + + public static String coerceToBoolean(String s, boolean isNamedAttribute) { + if (isNamedAttribute) { + return "(Boolean) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Boolean.class)"; + } else { + if (s == null || s.length() == 0) { + return "new Boolean(false)"; + } else { + // Detect format error at translation time + return "new Boolean(" + Boolean.valueOf(s).toString() + ")"; + } + } + } + + public static String coerceToPrimitiveByte(String s, + boolean isNamedAttribute) { + if (isNamedAttribute) { + return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToByte(" + s + ")"; + } else { + if (s == null || s.length() == 0) + return "(byte) 0"; + else + return "((byte)" + Byte.valueOf(s).toString() + ")"; + } + } + + public static String coerceToByte(String s, boolean isNamedAttribute) { + if (isNamedAttribute) { + return "(Byte) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Byte.class)"; + } else { + if (s == null || s.length() == 0) { + return "new Byte((byte) 0)"; + } else { + // Detect format error at translation time + return "new Byte((byte)" + Byte.valueOf(s).toString() + ")"; + } + } + } + + public static String coerceToChar(String s, boolean isNamedAttribute) { + if (isNamedAttribute) { + return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToChar(" + s + ")"; + } else { + if (s == null || s.length() == 0) { + return "(char) 0"; + } else { + char ch = s.charAt(0); + // this trick avoids escaping issues + return "((char) " + (int) ch + ")"; + } + } + } + + public static String coerceToCharacter(String s, boolean isNamedAttribute) { + if (isNamedAttribute) { + return "(Character) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Character.class)"; + } else { + if (s == null || s.length() == 0) { + return "new Character((char) 0)"; + } else { + char ch = s.charAt(0); + // this trick avoids escaping issues + return "new Character((char) " + (int) ch + ")"; + } + } + } + + public static String coerceToPrimitiveDouble(String s, + boolean isNamedAttribute) { + if (isNamedAttribute) { + return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToDouble(" + s + ")"; + } else { + if (s == null || s.length() == 0) + return "(double) 0"; + else + return Double.valueOf(s).toString(); + } + } + + public static String coerceToDouble(String s, boolean isNamedAttribute) { + if (isNamedAttribute) { + return "(Double) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Double.class)"; + } else { + if (s == null || s.length() == 0) { + return "new Double(0)"; + } else { + // Detect format error at translation time + return "new Double(" + Double.valueOf(s).toString() + ")"; + } + } + } + + public static String coerceToPrimitiveFloat(String s, + boolean isNamedAttribute) { + if (isNamedAttribute) { + return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToFloat(" + s + ")"; + } else { + if (s == null || s.length() == 0) + return "(float) 0"; + else + return Float.valueOf(s).toString() + "f"; + } + } + + public static String coerceToFloat(String s, boolean isNamedAttribute) { + if (isNamedAttribute) { + return "(Float) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Float.class)"; + } else { + if (s == null || s.length() == 0) { + return "new Float(0)"; + } else { + // Detect format error at translation time + return "new Float(" + Float.valueOf(s).toString() + "f)"; + } + } + } + + public static String coerceToInt(String s, boolean isNamedAttribute) { + if (isNamedAttribute) { + return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToInt(" + s + ")"; + } else { + if (s == null || s.length() == 0) + return "0"; + else + return Integer.valueOf(s).toString(); + } + } + + public static String coerceToInteger(String s, boolean isNamedAttribute) { + if (isNamedAttribute) { + return "(Integer) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Integer.class)"; + } else { + if (s == null || s.length() == 0) { + return "new Integer(0)"; + } else { + // Detect format error at translation time + return "new Integer(" + Integer.valueOf(s).toString() + ")"; + } + } + } + + public static String coerceToPrimitiveShort(String s, + boolean isNamedAttribute) { + if (isNamedAttribute) { + return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToShort(" + s + ")"; + } else { + if (s == null || s.length() == 0) + return "(short) 0"; + else + return "((short) " + Short.valueOf(s).toString() + ")"; + } + } + + public static String coerceToShort(String s, boolean isNamedAttribute) { + if (isNamedAttribute) { + return "(Short) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Short.class)"; + } else { + if (s == null || s.length() == 0) { + return "new Short((short) 0)"; + } else { + // Detect format error at translation time + return "new Short(\"" + Short.valueOf(s).toString() + "\")"; + } + } + } + + public static String coerceToPrimitiveLong(String s, + boolean isNamedAttribute) { + if (isNamedAttribute) { + return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToLong(" + s + ")"; + } else { + if (s == null || s.length() == 0) + return "(long) 0"; + else + return Long.valueOf(s).toString() + "l"; + } + } + + public static String coerceToLong(String s, boolean isNamedAttribute) { + if (isNamedAttribute) { + return "(Long) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Long.class)"; + } else { + if (s == null || s.length() == 0) { + return "new Long(0)"; + } else { + // Detect format error at translation time + return "new Long(" + Long.valueOf(s).toString() + "l)"; + } + } + } + + public static InputStream getInputStream(String fname, JarFile jarFile, + JspCompilationContext ctxt, + ErrorDispatcher err) + throws JasperException, IOException { + + InputStream in = null; + + if (jarFile != null) { + String jarEntryName = fname.substring(1, fname.length()); + ZipEntry jarEntry = jarFile.getEntry(jarEntryName); + if (jarEntry == null) { + err.jspError("jsp.error.file.not.found", fname); + } + in = jarFile.getInputStream(jarEntry); + } else { + in = ctxt.getResourceAsStream(fname); + } + + if (in == null) { + err.jspError("jsp.error.file.not.found", fname); + } + + return in; + } + + /** + * Gets the fully-qualified class name of the tag handler corresponding to + * the given tag file path. + * + * @param path Tag file path + * @param err Error dispatcher + * + * @return Fully-qualified class name of the tag handler corresponding to + * the given tag file path + * + * @deprecated Use {@link #getTagHandlerClassName(String, String, + * ErrorDispatcher) + * See https://issues.apache.org/bugzilla/show_bug.cgi?id=46471 + */ + public static String getTagHandlerClassName(String path, + ErrorDispatcher err) + throws JasperException { + return getTagHandlerClassName(path, null, err); + } + + /** + * Gets the fully-qualified class name of the tag handler corresponding to + * the given tag file path. + * + * @param path + * Tag file path + * @param err + * Error dispatcher + * + * @return Fully-qualified class name of the tag handler corresponding to + * the given tag file path + */ + public static String getTagHandlerClassName(String path, String urn, + ErrorDispatcher err) throws JasperException { + + String className = null; + int begin = 0; + int index; + + index = path.lastIndexOf(".tag"); + if (index == -1) { + err.jspError("jsp.error.tagfile.badSuffix", path); + } + + //It's tempting to remove the ".tag" suffix here, but we can't. + //If we remove it, the fully-qualified class name of this tag + //could conflict with the package name of other tags. + //For instance, the tag file + // /WEB-INF/tags/foo.tag + //would have fully-qualified class name + // org.apache.jsp.tag.web.foo + //which would conflict with the package name of the tag file + // /WEB-INF/tags/foo/bar.tag + + index = path.indexOf(WEB_INF_TAGS); + if (index != -1) { + className = "org.apache.jsp.tag.web."; + begin = index + WEB_INF_TAGS.length(); + } else { + index = path.indexOf(META_INF_TAGS); + if (index != -1) { + className = getClassNameBase(urn); + begin = index + META_INF_TAGS.length(); + } else { + err.jspError("jsp.error.tagfile.illegalPath", path); + } + } + + className += makeJavaPackage(path.substring(begin)); + + return className; + } + + private static String getClassNameBase(String urn) { + StringBuffer base = new StringBuffer("org.apache.jsp.tag.meta."); + if (urn != null) { + base.append(makeJavaPackage(urn)); + base.append('.'); + } + return base.toString(); + } + + /** + * Converts the given path to a Java package or fully-qualified class name + * + * @param path Path to convert + * + * @return Java package corresponding to the given path + */ + public static final String makeJavaPackage(String path) { + String classNameComponents[] = split(path,"/"); + StringBuffer legalClassNames = new StringBuffer(); + for (int i = 0; i < classNameComponents.length; i++) { + legalClassNames.append(makeJavaIdentifier(classNameComponents[i])); + if (i < classNameComponents.length - 1) { + legalClassNames.append('.'); + } + } + return legalClassNames.toString(); + } + + /** + * Splits a string into it's components. + * @param path String to split + * @param pat Pattern to split at + * @return the components of the path + */ + private static final String [] split(String path, String pat) { + Vector comps = new Vector(); + int pos = path.indexOf(pat); + int start = 0; + while( pos >= 0 ) { + if(pos > start ) { + String comp = path.substring(start,pos); + comps.add(comp); + } + start = pos + pat.length(); + pos = path.indexOf(pat,start); + } + if( start < path.length()) { + comps.add(path.substring(start)); + } + String [] result = new String[comps.size()]; + for(int i=0; i < comps.size(); i++) { + result[i] = (String)comps.elementAt(i); + } + return result; + } + + /** + * Converts the given identifier to a legal Java identifier + * + * @param identifier Identifier to convert + * + * @return Legal Java identifier corresponding to the given identifier + */ + public static final String makeJavaIdentifier(String identifier) { + StringBuffer modifiedIdentifier = + new StringBuffer(identifier.length()); + if (!Character.isJavaIdentifierStart(identifier.charAt(0))) { + modifiedIdentifier.append('_'); + } + for (int i = 0; i < identifier.length(); i++) { + char ch = identifier.charAt(i); + if (Character.isJavaIdentifierPart(ch) && ch != '_') { + modifiedIdentifier.append(ch); + } else if (ch == '.') { + modifiedIdentifier.append('_'); + } else { + modifiedIdentifier.append(mangleChar(ch)); + } + } + if (isJavaKeyword(modifiedIdentifier.toString())) { + modifiedIdentifier.append('_'); + } + return modifiedIdentifier.toString(); + } + + /** + * Mangle the specified character to create a legal Java class name. + */ + public static final String mangleChar(char ch) { + char[] result = new char[5]; + result[0] = '_'; + result[1] = Character.forDigit((ch >> 12) & 0xf, 16); + result[2] = Character.forDigit((ch >> 8) & 0xf, 16); + result[3] = Character.forDigit((ch >> 4) & 0xf, 16); + result[4] = Character.forDigit(ch & 0xf, 16); + return new String(result); + } + + /** + * Test whether the argument is a Java keyword + */ + public static boolean isJavaKeyword(String key) { + int i = 0; + int j = javaKeywords.length; + while (i < j) { + int k = (i+j)/2; + int result = javaKeywords[k].compareTo(key); + if (result == 0) { + return true; + } + if (result < 0) { + i = k+1; + } else { + j = k; + } + } + return false; + } + + /** + * Converts the given Xml name to a legal Java identifier. This is + * slightly more efficient than makeJavaIdentifier in that we only need + * to worry about '.', '-', and ':' in the string. We also assume that + * the resultant string is further concatenated with some prefix string + * so that we don't have to worry about it being a Java key word. + * + * @param name Identifier to convert + * + * @return Legal Java identifier corresponding to the given identifier + */ + public static final String makeXmlJavaIdentifier(String name) { + if (name.indexOf('-') >= 0) + name = replace(name, '-', "$1"); + if (name.indexOf('.') >= 0) + name = replace(name, '.', "$2"); + if (name.indexOf(':') >= 0) + name = replace(name, ':', "$3"); + return name; + } + + static InputStreamReader getReader(String fname, String encoding, + JarFile jarFile, + JspCompilationContext ctxt, + ErrorDispatcher err) + throws JasperException, IOException { + + return getReader(fname, encoding, jarFile, ctxt, err, 0); + } + + static InputStreamReader getReader(String fname, String encoding, + JarFile jarFile, + JspCompilationContext ctxt, + ErrorDispatcher err, int skip) + throws JasperException, IOException { + + InputStreamReader reader = null; + InputStream in = getInputStream(fname, jarFile, ctxt, err); + for (int i = 0; i < skip; i++) { + in.read(); + } + try { + reader = new InputStreamReader(in, encoding); + } catch (UnsupportedEncodingException ex) { + err.jspError("jsp.error.unsupported.encoding", encoding); + } + + return reader; + } + + /** + * Handles taking input from TLDs + * 'java.lang.Object' -> 'java.lang.Object.class' + * 'int' -> 'int.class' + * 'void' -> 'Void.TYPE' + * 'int[]' -> 'int[].class' + * + * @param type + * @return + */ + public static String toJavaSourceTypeFromTld(String type) { + if (type == null || "void".equals(type)) { + return "Void.TYPE"; + } + return type + ".class"; + } + + /** + * Class.getName() return arrays in the form "[[[", where et, + * the element type can be one of ZBCDFIJS or L; + * It is converted into forms that can be understood by javac. + */ + public static String toJavaSourceType(String type) { + + if (type.charAt(0) != '[') { + return type; + } + + int dims = 1; + String t = null; + for (int i = 1; i < type.length(); i++) { + if (type.charAt(i) == '[') { + dims++; + } else { + switch (type.charAt(i)) { + case 'Z': t = "boolean"; break; + case 'B': t = "byte"; break; + case 'C': t = "char"; break; + case 'D': t = "double"; break; + case 'F': t = "float"; break; + case 'I': t = "int"; break; + case 'J': t = "long"; break; + case 'S': t = "short"; break; + case 'L': t = type.substring(i+1, type.indexOf(';')); break; + } + break; + } + } + StringBuffer resultType = new StringBuffer(t); + for (; dims > 0; dims--) { + resultType.append("[]"); + } + return resultType.toString(); + } + + /** + * Compute the canonical name from a Class instance. Note that a + * simple replacment of '$' with '.' of a binary name would not work, + * as '$' is a legal Java Identifier character. + * @param c A instance of java.lang.Class + * @return The canonical name of c. + */ + public static String getCanonicalName(Class c) { + + String binaryName = c.getName(); + c = c.getDeclaringClass(); + + if (c == null) { + return binaryName; + } + + StringBuffer buf = new StringBuffer(binaryName); + do { + buf.setCharAt(c.getName().length(), '.'); + c = c.getDeclaringClass(); + } while ( c != null); + + return buf.toString(); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Localizer.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Localizer.java new file mode 100644 index 000000000..260ac51be --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Localizer.java @@ -0,0 +1,160 @@ +/* + * 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.text.MessageFormat; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * Class responsible for converting error codes to corresponding localized + * error messages. + * + * @author Jan Luehe + */ +public class Localizer { + + private static ResourceBundle bundle = null; + + static { + try { + bundle = ResourceBundle.getBundle( + "org.apache.jasper.resources.LocalStrings"); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + /* + * Returns the localized error message corresponding to the given error + * code. + * + * If the given error code is not defined in the resource bundle for + * localized error messages, it is used as the error message. + * + * @param errCode Error code to localize + * + * @return Localized error message + */ + public static String getMessage(String errCode) { + String errMsg = errCode; + try { + errMsg = bundle.getString(errCode); + } catch (MissingResourceException e) { + } + return errMsg; + } + + /* + * Returns the localized error message corresponding to the given error + * code. + * + * If the given error code is not defined in the resource bundle for + * localized error messages, it is used as the error message. + * + * @param errCode Error code to localize + * @param arg Argument for parametric replacement + * + * @return Localized error message + */ + public static String getMessage(String errCode, String arg) { + return getMessage(errCode, new Object[] {arg}); + } + + /* + * Returns the localized error message corresponding to the given error + * code. + * + * If the given error code is not defined in the resource bundle for + * localized error messages, it is used as the error message. + * + * @param errCode Error code to localize + * @param arg1 First argument for parametric replacement + * @param arg2 Second argument for parametric replacement + * + * @return Localized error message + */ + public static String getMessage(String errCode, String arg1, String arg2) { + return getMessage(errCode, new Object[] {arg1, arg2}); + } + + /* + * Returns the localized error message corresponding to the given error + * code. + * + * If the given error code is not defined in the resource bundle for + * localized error messages, it is used as the error message. + * + * @param errCode Error code to localize + * @param arg1 First argument for parametric replacement + * @param arg2 Second argument for parametric replacement + * @param arg3 Third argument for parametric replacement + * + * @return Localized error message + */ + public static String getMessage(String errCode, String arg1, String arg2, + String arg3) { + return getMessage(errCode, new Object[] {arg1, arg2, arg3}); + } + + /* + * Returns the localized error message corresponding to the given error + * code. + * + * If the given error code is not defined in the resource bundle for + * localized error messages, it is used as the error message. + * + * @param errCode Error code to localize + * @param arg1 First argument for parametric replacement + * @param arg2 Second argument for parametric replacement + * @param arg3 Third argument for parametric replacement + * @param arg4 Fourth argument for parametric replacement + * + * @return Localized error message + */ + public static String getMessage(String errCode, String arg1, String arg2, + String arg3, String arg4) { + return getMessage(errCode, new Object[] {arg1, arg2, arg3, arg4}); + } + + /* + * Returns the localized error message corresponding to the given error + * code. + * + * If the given error code is not defined in the resource bundle for + * localized error messages, it is used as the error message. + * + * @param errCode Error code to localize + * @param args Arguments for parametric replacement + * + * @return Localized error message + */ + public static String getMessage(String errCode, Object[] args) { + String errMsg = errCode; + try { + errMsg = bundle.getString(errCode); + if (args != null) { + MessageFormat formatter = new MessageFormat(errMsg); + errMsg = formatter.format(args); + } + } catch (MissingResourceException e) { + } + + return errMsg; + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Mark.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Mark.java new file mode 100644 index 000000000..85c942bfe --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Mark.java @@ -0,0 +1,282 @@ +/* + * 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.util.Stack; +import java.net.URL; +import java.net.MalformedURLException; +import org.apache.jasper.JspCompilationContext; + +/** + * Mark represents a point in the JSP input. + * + * @author Anil K. Vijendran + */ +final class Mark { + + // position within current stream + int cursor, line, col; + + // directory of file for current stream + String baseDir; + + // current stream + char[] stream = null; + + // fileid of current stream + private int fileId; + + // name of the current file + private String fileName; + + /* + * stack of stream and stream state of streams that have included + * current stream + */ + private Stack includeStack = null; + + // encoding of current file + private String encoding = null; + + // reader that owns this mark (so we can look up fileid's) + private JspReader reader; + + private JspCompilationContext ctxt; + + /** + * Constructor + * + * @param reader JspReader this mark belongs to + * @param inStream current stream for this mark + * @param fileId id of requested jsp file + * @param name JSP file name + * @param inBaseDir base directory of requested jsp file + * @param inEncoding encoding of current file + */ + Mark(JspReader reader, char[] inStream, int fileId, String name, + String inBaseDir, String inEncoding) { + + this.reader = reader; + this.ctxt = reader.getJspCompilationContext(); + this.stream = inStream; + this.cursor = 0; + this.line = 1; + this.col = 1; + this.fileId = fileId; + this.fileName = name; + this.baseDir = inBaseDir; + this.encoding = inEncoding; + this.includeStack = new Stack(); + } + + + /** + * Constructor + */ + Mark(Mark other) { + + this.reader = other.reader; + this.ctxt = other.reader.getJspCompilationContext(); + this.stream = other.stream; + this.fileId = other.fileId; + this.fileName = other.fileName; + this.cursor = other.cursor; + this.line = other.line; + this.col = other.col; + this.baseDir = other.baseDir; + this.encoding = other.encoding; + + // clone includeStack without cloning contents + includeStack = new Stack(); + for ( int i=0; i < other.includeStack.size(); i++ ) { + includeStack.addElement( other.includeStack.elementAt(i) ); + } + } + + + /** + * Constructor + */ + Mark(JspCompilationContext ctxt, String filename, int line, int col) { + + this.reader = null; + this.ctxt = ctxt; + this.stream = null; + this.cursor = 0; + this.line = line; + this.col = col; + this.fileId = -1; + this.fileName = filename; + this.baseDir = "le-basedir"; + this.encoding = "le-endocing"; + this.includeStack = null; + } + + + /** + * Sets this mark's state to a new stream. + * It will store the current stream in it's includeStack. + * + * @param inStream new stream for mark + * @param inFileId id of new file from which stream comes from + * @param inBaseDir directory of file + * @param inEncoding encoding of new file + */ + public void pushStream(char[] inStream, int inFileId, String name, + String inBaseDir, String inEncoding) + { + // store current state in stack + includeStack.push(new IncludeState(cursor, line, col, fileId, + fileName, baseDir, + encoding, stream) ); + + // set new variables + cursor = 0; + line = 1; + col = 1; + fileId = inFileId; + fileName = name; + baseDir = inBaseDir; + encoding = inEncoding; + stream = inStream; + } + + + /** + * Restores this mark's state to a previously stored stream. + * @return The previous Mark instance when the stream was pushed, or null + * if there is no previous stream + */ + public Mark popStream() { + // make sure we have something to pop + if ( includeStack.size() <= 0 ) { + return null; + } + + // get previous state in stack + IncludeState state = (IncludeState) includeStack.pop( ); + + // set new variables + cursor = state.cursor; + line = state.line; + col = state.col; + fileId = state.fileId; + fileName = state.fileName; + baseDir = state.baseDir; + stream = state.stream; + return this; + } + + + // -------------------- Locator interface -------------------- + + public int getLineNumber() { + return line; + } + + public int getColumnNumber() { + return col; + } + + public String getSystemId() { + return getFile(); + } + + public String getPublicId() { + return null; + } + + public String toString() { + return getFile()+"("+line+","+col+")"; + } + + public String getFile() { + return this.fileName; + } + + /** + * Gets the URL of the resource with which this Mark is associated + * + * @return URL of the resource with which this Mark is associated + * + * @exception MalformedURLException if the resource pathname is incorrect + */ + public URL getURL() throws MalformedURLException { + return ctxt.getResource(getFile()); + } + + public String toShortString() { + return "("+line+","+col+")"; + } + + public boolean equals(Object other) { + if (other instanceof Mark) { + Mark m = (Mark) other; + return this.reader == m.reader && this.fileId == m.fileId + && this.cursor == m.cursor && this.line == m.line + && this.col == m.col; + } + return false; + } + + /** + * @return true if this Mark is greather than the other + * Mark, false otherwise. + */ + public boolean isGreater(Mark other) { + + boolean greater = false; + + if (this.line > other.line) { + greater = true; + } else if (this.line == other.line && this.col > other.col) { + greater = true; + } + + return greater; + } + + /** + * Keep track of parser before parsing an included file. + * This class keeps track of the parser before we switch to parsing an + * included file. In other words, it's the parser's continuation to be + * reinstalled after the included file parsing is done. + */ + class IncludeState { + int cursor, line, col; + int fileId; + String fileName; + String baseDir; + String encoding; + char[] stream = null; + + IncludeState(int inCursor, int inLine, int inCol, int inFileId, + String name, String inBaseDir, String inEncoding, + char[] inStream) { + cursor = inCursor; + line = inLine; + col = inCol; + fileId = inFileId; + fileName = name; + baseDir = inBaseDir; + encoding = inEncoding; + stream = inStream; + } + } + +} + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Node.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Node.java new file mode 100644 index 000000000..2aa504fdc --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Node.java @@ -0,0 +1,2567 @@ +/* + * 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.util.Iterator; +import java.util.List; +import java.util.Vector; +import java.util.ArrayList; + +import javax.el.ELContext; +import javax.el.ELException; +import javax.el.ExpressionFactory; +import javax.el.ValueExpression; +import javax.servlet.jsp.tagext.BodyTag; +import javax.servlet.jsp.tagext.DynamicAttributes; +import javax.servlet.jsp.tagext.IterationTag; +import javax.servlet.jsp.tagext.JspIdConsumer; +import javax.servlet.jsp.tagext.SimpleTag; +import javax.servlet.jsp.tagext.TagAttributeInfo; +import javax.servlet.jsp.tagext.TagData; +import javax.servlet.jsp.tagext.TagFileInfo; +import javax.servlet.jsp.tagext.TagInfo; +import javax.servlet.jsp.tagext.TagVariableInfo; +import javax.servlet.jsp.tagext.TryCatchFinally; +import javax.servlet.jsp.tagext.VariableInfo; + +import org.apache.jasper.Constants; +import org.apache.jasper.JasperException; +import org.apache.jasper.compiler.tagplugin.TagPluginContext; +import org.xml.sax.Attributes; + +/** + * An internal data representation of a JSP page or a JSP docuement (XML). Also + * included here is a visitor class for tranversing nodes. + * + * @author Kin-man Chung + * @author Jan Luehe + * @author Shawn Bayern + * @author Mark Roth + */ + +abstract class Node implements TagConstants { + + private static final VariableInfo[] ZERO_VARIABLE_INFO = {}; + + protected Attributes attrs; + + // xmlns attributes that represent tag libraries (only in XML syntax) + protected Attributes taglibAttrs; + + /* + * xmlns attributes that do not represent tag libraries (only in XML syntax) + */ + protected Attributes nonTaglibXmlnsAttrs; + + protected Nodes body; + + protected String text; + + protected Mark startMark; + + protected int beginJavaLine; + + protected int endJavaLine; + + protected Node parent; + + protected Nodes namedAttributeNodes; // cached for performance + + protected String qName; + + protected String localName; + + /* + * The name of the inner class to which the codes for this node and its body + * are generated. For instance, for in foo.jsp, this is + * "foo_jspHelper". This is primarily used for communicating such info from + * Generator to Smap generator. + */ + protected String innerClassName; + + private boolean isDummy; + + /** + * Zero-arg Constructor. + */ + public Node() { + this.isDummy = true; + } + + /** + * Constructor. + * + * @param start + * The location of the jsp page + * @param parent + * The enclosing node + */ + public Node(Mark start, Node parent) { + this.startMark = start; + this.isDummy = (start == null); + addToParent(parent); + } + + /** + * Constructor. + * + * @param qName + * The action's qualified name + * @param localName + * The action's local name + * @param start + * The location of the jsp page + * @param parent + * The enclosing node + */ + public Node(String qName, String localName, Mark start, Node parent) { + this.qName = qName; + this.localName = localName; + this.startMark = start; + this.isDummy = (start == null); + addToParent(parent); + } + + /** + * Constructor for Nodes parsed from standard syntax. + * + * @param qName + * The action's qualified name + * @param localName + * The action's local name + * @param attrs + * The attributes for this node + * @param start + * The location of the jsp page + * @param parent + * The enclosing node + */ + public Node(String qName, String localName, Attributes attrs, Mark start, + Node parent) { + this.qName = qName; + this.localName = localName; + this.attrs = attrs; + this.startMark = start; + this.isDummy = (start == null); + addToParent(parent); + } + + /** + * Constructor for Nodes parsed from XML syntax. + * + * @param qName + * The action's qualified name + * @param localName + * The action's local name + * @param attrs + * The action's attributes whose name does not start with xmlns + * @param nonTaglibXmlnsAttrs + * The action's xmlns attributes that do not represent tag + * libraries + * @param taglibAttrs + * The action's xmlns attributes that represent tag libraries + * @param start + * The location of the jsp page + * @param parent + * The enclosing node + */ + public Node(String qName, String localName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, Mark start, + Node parent) { + this.qName = qName; + this.localName = localName; + this.attrs = attrs; + this.nonTaglibXmlnsAttrs = nonTaglibXmlnsAttrs; + this.taglibAttrs = taglibAttrs; + this.startMark = start; + this.isDummy = (start == null); + addToParent(parent); + } + + /* + * Constructor. + * + * @param qName The action's qualified name @param localName The action's + * local name @param text The text associated with this node @param start + * The location of the jsp page @param parent The enclosing node + */ + public Node(String qName, String localName, String text, Mark start, + Node parent) { + this.qName = qName; + this.localName = localName; + this.text = text; + this.startMark = start; + this.isDummy = (start == null); + addToParent(parent); + } + + public String getQName() { + return this.qName; + } + + public String getLocalName() { + return this.localName; + } + + /* + * Gets this Node's attributes. + * + * In the case of a Node parsed from standard syntax, this method returns + * all the Node's attributes. + * + * In the case of a Node parsed from XML syntax, this method returns only + * those attributes whose name does not start with xmlns. + */ + public Attributes getAttributes() { + return this.attrs; + } + + /* + * Gets this Node's xmlns attributes that represent tag libraries (only + * meaningful for Nodes parsed from XML syntax) + */ + public Attributes getTaglibAttributes() { + return this.taglibAttrs; + } + + /* + * Gets this Node's xmlns attributes that do not represent tag libraries + * (only meaningful for Nodes parsed from XML syntax) + */ + public Attributes getNonTaglibXmlnsAttributes() { + return this.nonTaglibXmlnsAttrs; + } + + public void setAttributes(Attributes attrs) { + this.attrs = attrs; + } + + public String getAttributeValue(String name) { + return (attrs == null) ? null : attrs.getValue(name); + } + + /** + * Get the attribute that is non request time expression, either from the + * attribute of the node, or from a jsp:attrbute + */ + public String getTextAttribute(String name) { + + String attr = getAttributeValue(name); + if (attr != null) { + return attr; + } + + NamedAttribute namedAttribute = getNamedAttributeNode(name); + if (namedAttribute == null) { + return null; + } + + return namedAttribute.getText(); + } + + /** + * Searches all subnodes of this node for jsp:attribute standard actions + * with the given name, and returns the NamedAttribute node of the matching + * named attribute, nor null if no such node is found. + *

+ * This should always be called and only be called for nodes that accept + * dynamic runtime attribute expressions. + */ + public NamedAttribute getNamedAttributeNode(String name) { + NamedAttribute result = null; + + // Look for the attribute in NamedAttribute children + Nodes nodes = getNamedAttributeNodes(); + int numChildNodes = nodes.size(); + for (int i = 0; i < numChildNodes; i++) { + NamedAttribute na = (NamedAttribute) nodes.getNode(i); + boolean found = false; + int index = name.indexOf(':'); + if (index != -1) { + // qualified name + found = na.getName().equals(name); + } else { + found = na.getLocalName().equals(name); + } + if (found) { + result = na; + break; + } + } + + return result; + } + + /** + * Searches all subnodes of this node for jsp:attribute standard actions, + * and returns that set of nodes as a Node.Nodes object. + * + * @return Possibly empty Node.Nodes object containing any jsp:attribute + * subnodes of this Node + */ + public Node.Nodes getNamedAttributeNodes() { + + if (namedAttributeNodes != null) { + return namedAttributeNodes; + } + + Node.Nodes result = new Node.Nodes(); + + // Look for the attribute in NamedAttribute children + Nodes nodes = getBody(); + if (nodes != null) { + int numChildNodes = nodes.size(); + for (int i = 0; i < numChildNodes; i++) { + Node n = nodes.getNode(i); + if (n instanceof NamedAttribute) { + result.add(n); + } else if (!(n instanceof Comment)) { + // Nothing can come before jsp:attribute, and only + // jsp:body can come after it. + break; + } + } + } + + namedAttributeNodes = result; + return result; + } + + public Nodes getBody() { + return body; + } + + public void setBody(Nodes body) { + this.body = body; + } + + public String getText() { + return text; + } + + public Mark getStart() { + return startMark; + } + + public Node getParent() { + return parent; + } + + public int getBeginJavaLine() { + return beginJavaLine; + } + + public void setBeginJavaLine(int begin) { + beginJavaLine = begin; + } + + public int getEndJavaLine() { + return endJavaLine; + } + + public void setEndJavaLine(int end) { + endJavaLine = end; + } + + public boolean isDummy() { + return isDummy; + } + + public Node.Root getRoot() { + Node n = this; + while (!(n instanceof Node.Root)) { + n = n.getParent(); + } + return (Node.Root) n; + } + + public String getInnerClassName() { + return innerClassName; + } + + public void setInnerClassName(String icn) { + innerClassName = icn; + } + + /** + * Selects and invokes a method in the visitor class based on the node type. + * This is abstract and should be overrode by the extending classes. + * + * @param v + * The visitor class + */ + abstract void accept(Visitor v) throws JasperException; + + // ********************************************************************* + // Private utility methods + + /* + * Adds this Node to the body of the given parent. + */ + private void addToParent(Node parent) { + if (parent != null) { + this.parent = parent; + Nodes parentBody = parent.getBody(); + if (parentBody == null) { + parentBody = new Nodes(); + parent.setBody(parentBody); + } + parentBody.add(this); + } + } + + /*************************************************************************** + * Child classes + */ + + /** + * Represents the root of a Jsp page or Jsp document + */ + public static class Root extends Node { + + private Root parentRoot; + + private boolean isXmlSyntax; + + // Source encoding of the page containing this Root + private String pageEnc; + + // Page encoding specified in JSP config element + private String jspConfigPageEnc; + + /* + * Flag indicating if the default page encoding is being used (only + * applicable with standard syntax). + * + * True if the page does not provide a page directive with a + * 'contentType' attribute (or the 'contentType' attribute doesn't have + * a CHARSET value), the page does not provide a page directive with a + * 'pageEncoding' attribute, and there is no JSP configuration element + * page-encoding whose URL pattern matches the page. + */ + private boolean isDefaultPageEncoding; + + /* + * Indicates whether an encoding has been explicitly specified in the + * page's XML prolog (only used for pages in XML syntax). This + * information is used to decide whether a translation error must be + * reported for encoding conflicts. + */ + private boolean isEncodingSpecifiedInProlog; + + /* + * Indicates whether an encoding has been explicitly specified in the + * page's bom. + */ + private boolean isBomPresent; + + /* + * Sequence number for temporary variables. + */ + private int tempSequenceNumber = 0; + + /* + * Constructor. + */ + Root(Mark start, Node parent, boolean isXmlSyntax) { + super(start, parent); + this.isXmlSyntax = isXmlSyntax; + this.qName = JSP_ROOT_ACTION; + this.localName = ROOT_ACTION; + + // Figure out and set the parent root + Node r = parent; + while ((r != null) && !(r instanceof Node.Root)) + r = r.getParent(); + parentRoot = (Node.Root) r; + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public boolean isXmlSyntax() { + return isXmlSyntax; + } + + /* + * Sets the encoding specified in the JSP config element whose URL + * pattern matches the page containing this Root. + */ + public void setJspConfigPageEncoding(String enc) { + jspConfigPageEnc = enc; + } + + /* + * Gets the encoding specified in the JSP config element whose URL + * pattern matches the page containing this Root. + */ + public String getJspConfigPageEncoding() { + return jspConfigPageEnc; + } + + public void setPageEncoding(String enc) { + pageEnc = enc; + } + + public String getPageEncoding() { + return pageEnc; + } + + public void setIsDefaultPageEncoding(boolean isDefault) { + isDefaultPageEncoding = isDefault; + } + + public boolean isDefaultPageEncoding() { + return isDefaultPageEncoding; + } + + public void setIsEncodingSpecifiedInProlog(boolean isSpecified) { + isEncodingSpecifiedInProlog = isSpecified; + } + + public boolean isEncodingSpecifiedInProlog() { + return isEncodingSpecifiedInProlog; + } + + public void setIsBomPresent(boolean isBom) { + isBomPresent = isBom; + } + + public boolean isBomPresent() { + return isBomPresent; + } + + /** + * @return The enclosing root to this Root. Usually represents the page + * that includes this one. + */ + public Root getParentRoot() { + return parentRoot; + } + + /** + * Generates a new temporary variable name. + */ + public String nextTemporaryVariableName() { + if (parentRoot == null) { + return Constants.TEMP_VARIABLE_NAME_PREFIX + (tempSequenceNumber++); + } else { + return parentRoot.nextTemporaryVariableName(); + } + + } + } + + /** + * Represents the root of a Jsp document (XML syntax) + */ + public static class JspRoot extends Node { + + public JspRoot(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, ROOT_ACTION, attrs, nonTaglibXmlnsAttrs, taglibAttrs, + start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents a page directive + */ + public static class PageDirective extends Node { + + private Vector imports; + + public PageDirective(Attributes attrs, Mark start, Node parent) { + this(JSP_PAGE_DIRECTIVE_ACTION, attrs, null, null, start, parent); + } + + public PageDirective(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, PAGE_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + imports = new Vector(); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + /** + * Parses the comma-separated list of class or package names in the + * given attribute value and adds each component to this PageDirective's + * vector of imported classes and packages. + * + * @param value + * A comma-separated string of imports. + */ + public void addImport(String value) { + int start = 0; + int index; + while ((index = value.indexOf(',', start)) != -1) { + imports.add(value.substring(start, index).trim()); + start = index + 1; + } + if (start == 0) { + // No comma found + imports.add(value.trim()); + } else { + imports.add(value.substring(start).trim()); + } + } + + public List getImports() { + return imports; + } + } + + /** + * Represents an include directive + */ + public static class IncludeDirective extends Node { + + public IncludeDirective(Attributes attrs, Mark start, Node parent) { + this(JSP_INCLUDE_DIRECTIVE_ACTION, attrs, null, null, start, parent); + } + + public IncludeDirective(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, INCLUDE_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents a custom taglib directive + */ + public static class TaglibDirective extends Node { + + public TaglibDirective(Attributes attrs, Mark start, Node parent) { + super(JSP_TAGLIB_DIRECTIVE_ACTION, TAGLIB_DIRECTIVE_ACTION, attrs, + start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents a tag directive + */ + public static class TagDirective extends Node { + private Vector imports; + + public TagDirective(Attributes attrs, Mark start, Node parent) { + this(JSP_TAG_DIRECTIVE_ACTION, attrs, null, null, start, parent); + } + + public TagDirective(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, TAG_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + imports = new Vector(); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + /** + * Parses the comma-separated list of class or package names in the + * given attribute value and adds each component to this PageDirective's + * vector of imported classes and packages. + * + * @param value + * A comma-separated string of imports. + */ + public void addImport(String value) { + int start = 0; + int index; + while ((index = value.indexOf(',', start)) != -1) { + imports.add(value.substring(start, index).trim()); + start = index + 1; + } + if (start == 0) { + // No comma found + imports.add(value.trim()); + } else { + imports.add(value.substring(start).trim()); + } + } + + public List getImports() { + return imports; + } + } + + /** + * Represents an attribute directive + */ + public static class AttributeDirective extends Node { + + public AttributeDirective(Attributes attrs, Mark start, Node parent) { + this(JSP_ATTRIBUTE_DIRECTIVE_ACTION, attrs, null, null, start, + parent); + } + + public AttributeDirective(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, ATTRIBUTE_DIRECTIVE_ACTION, attrs, + nonTaglibXmlnsAttrs, taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents a variable directive + */ + public static class VariableDirective extends Node { + + public VariableDirective(Attributes attrs, Mark start, Node parent) { + this(JSP_VARIABLE_DIRECTIVE_ACTION, attrs, null, null, start, + parent); + } + + public VariableDirective(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, VARIABLE_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents a tag file action + */ + public static class InvokeAction extends Node { + + public InvokeAction(Attributes attrs, Mark start, Node parent) { + this(JSP_INVOKE_ACTION, attrs, null, null, start, parent); + } + + public InvokeAction(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, INVOKE_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents a tag file action + */ + public static class DoBodyAction extends Node { + + public DoBodyAction(Attributes attrs, Mark start, Node parent) { + this(JSP_DOBODY_ACTION, attrs, null, null, start, parent); + } + + public DoBodyAction(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, DOBODY_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents a Jsp comment Comments are kept for completeness. + */ + public static class Comment extends Node { + + public Comment(String text, Mark start, Node parent) { + super(null, null, text, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents an expression, declaration, or scriptlet + */ + public static abstract class ScriptingElement extends Node { + + public ScriptingElement(String qName, String localName, String text, + Mark start, Node parent) { + super(qName, localName, text, start, parent); + } + + public ScriptingElement(String qName, String localName, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, localName, null, nonTaglibXmlnsAttrs, taglibAttrs, + start, parent); + } + + /** + * When this node was created from a JSP page in JSP syntax, its text + * was stored as a String in the "text" field, whereas when this node + * was created from a JSP document, its text was stored as one or more + * TemplateText nodes in its body. This method handles either case. + * + * @return The text string + */ + public String getText() { + String ret = text; + if (ret == null) { + if (body != null) { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < body.size(); i++) { + buf.append(body.getNode(i).getText()); + } + ret = buf.toString(); + } else { + // Nulls cause NPEs further down the line + ret = ""; + } + } + return ret; + } + + /** + * For the same reason as above, the source line information in the + * contained TemplateText node should be used. + */ + public Mark getStart() { + if (text == null && body != null && body.size() > 0) { + return body.getNode(0).getStart(); + } else { + return super.getStart(); + } + } + } + + /** + * Represents a declaration + */ + public static class Declaration extends ScriptingElement { + + public Declaration(String text, Mark start, Node parent) { + super(JSP_DECLARATION_ACTION, DECLARATION_ACTION, text, start, + parent); + } + + public Declaration(String qName, Attributes nonTaglibXmlnsAttrs, + Attributes taglibAttrs, Mark start, Node parent) { + super(qName, DECLARATION_ACTION, nonTaglibXmlnsAttrs, taglibAttrs, + start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents an expression. Expressions in attributes are embedded in the + * attribute string and not here. + */ + public static class Expression extends ScriptingElement { + + public Expression(String text, Mark start, Node parent) { + super(JSP_EXPRESSION_ACTION, EXPRESSION_ACTION, text, start, parent); + } + + public Expression(String qName, Attributes nonTaglibXmlnsAttrs, + Attributes taglibAttrs, Mark start, Node parent) { + super(qName, EXPRESSION_ACTION, nonTaglibXmlnsAttrs, taglibAttrs, + start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents a scriptlet + */ + public static class Scriptlet extends ScriptingElement { + + public Scriptlet(String text, Mark start, Node parent) { + super(JSP_SCRIPTLET_ACTION, SCRIPTLET_ACTION, text, start, parent); + } + + public Scriptlet(String qName, Attributes nonTaglibXmlnsAttrs, + Attributes taglibAttrs, Mark start, Node parent) { + super(qName, SCRIPTLET_ACTION, nonTaglibXmlnsAttrs, taglibAttrs, + start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents an EL expression. Expressions in attributes are embedded in + * the attribute string and not here. + */ + public static class ELExpression extends Node { + + private ELNode.Nodes el; + + private final char type; + + public ELExpression(char type, String text, Mark start, Node parent) { + super(null, null, text, start, parent); + this.type = type; + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public void setEL(ELNode.Nodes el) { + this.el = el; + } + + public ELNode.Nodes getEL() { + return el; + } + + public char getType() { + return this.type; + } + } + + /** + * Represents a param action + */ + public static class ParamAction extends Node { + + JspAttribute value; + + public ParamAction(Attributes attrs, Mark start, Node parent) { + this(JSP_PARAM_ACTION, attrs, null, null, start, parent); + } + + public ParamAction(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, PARAM_ACTION, attrs, nonTaglibXmlnsAttrs, taglibAttrs, + start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public void setValue(JspAttribute value) { + this.value = value; + } + + public JspAttribute getValue() { + return value; + } + } + + /** + * Represents a params action + */ + public static class ParamsAction extends Node { + + public ParamsAction(Mark start, Node parent) { + this(JSP_PARAMS_ACTION, null, null, start, parent); + } + + public ParamsAction(String qName, Attributes nonTaglibXmlnsAttrs, + Attributes taglibAttrs, Mark start, Node parent) { + super(qName, PARAMS_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs, + start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents a fallback action + */ + public static class FallBackAction extends Node { + + public FallBackAction(Mark start, Node parent) { + this(JSP_FALLBACK_ACTION, null, null, start, parent); + } + + public FallBackAction(String qName, Attributes nonTaglibXmlnsAttrs, + Attributes taglibAttrs, Mark start, Node parent) { + super(qName, FALLBACK_ACTION, null, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents an include action + */ + public static class IncludeAction extends Node { + + private JspAttribute page; + + public IncludeAction(Attributes attrs, Mark start, Node parent) { + this(JSP_INCLUDE_ACTION, attrs, null, null, start, parent); + } + + public IncludeAction(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, INCLUDE_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public void setPage(JspAttribute page) { + this.page = page; + } + + public JspAttribute getPage() { + return page; + } + } + + /** + * Represents a forward action + */ + public static class ForwardAction extends Node { + + private JspAttribute page; + + public ForwardAction(Attributes attrs, Mark start, Node parent) { + this(JSP_FORWARD_ACTION, attrs, null, null, start, parent); + } + + public ForwardAction(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, FORWARD_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public void setPage(JspAttribute page) { + this.page = page; + } + + public JspAttribute getPage() { + return page; + } + } + + /** + * Represents a getProperty action + */ + public static class GetProperty extends Node { + + public GetProperty(Attributes attrs, Mark start, Node parent) { + this(JSP_GET_PROPERTY_ACTION, attrs, null, null, start, parent); + } + + public GetProperty(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, GET_PROPERTY_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents a setProperty action + */ + public static class SetProperty extends Node { + + private JspAttribute value; + + public SetProperty(Attributes attrs, Mark start, Node parent) { + this(JSP_SET_PROPERTY_ACTION, attrs, null, null, start, parent); + } + + public SetProperty(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, SET_PROPERTY_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public void setValue(JspAttribute value) { + this.value = value; + } + + public JspAttribute getValue() { + return value; + } + } + + /** + * Represents a useBean action + */ + public static class UseBean extends Node { + + JspAttribute beanName; + + public UseBean(Attributes attrs, Mark start, Node parent) { + this(JSP_USE_BEAN_ACTION, attrs, null, null, start, parent); + } + + public UseBean(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, USE_BEAN_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public void setBeanName(JspAttribute beanName) { + this.beanName = beanName; + } + + public JspAttribute getBeanName() { + return beanName; + } + } + + /** + * Represents a plugin action + */ + public static class PlugIn extends Node { + + private JspAttribute width; + + private JspAttribute height; + + public PlugIn(Attributes attrs, Mark start, Node parent) { + this(JSP_PLUGIN_ACTION, attrs, null, null, start, parent); + } + + public PlugIn(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, PLUGIN_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public void setHeight(JspAttribute height) { + this.height = height; + } + + public void setWidth(JspAttribute width) { + this.width = width; + } + + public JspAttribute getHeight() { + return height; + } + + public JspAttribute getWidth() { + return width; + } + } + + /** + * Represents an uninterpreted tag, from a Jsp document + */ + public static class UninterpretedTag extends Node { + + private JspAttribute[] jspAttrs; + + public UninterpretedTag(String qName, String localName, + Attributes attrs, Attributes nonTaglibXmlnsAttrs, + Attributes taglibAttrs, Mark start, Node parent) { + super(qName, localName, attrs, nonTaglibXmlnsAttrs, taglibAttrs, + start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public void setJspAttributes(JspAttribute[] jspAttrs) { + this.jspAttrs = jspAttrs; + } + + public JspAttribute[] getJspAttributes() { + return jspAttrs; + } + } + + /** + * Represents a . + */ + public static class JspElement extends Node { + + private JspAttribute[] jspAttrs; + + private JspAttribute nameAttr; + + public JspElement(Attributes attrs, Mark start, Node parent) { + this(JSP_ELEMENT_ACTION, attrs, null, null, start, parent); + } + + public JspElement(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, ELEMENT_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public void setJspAttributes(JspAttribute[] jspAttrs) { + this.jspAttrs = jspAttrs; + } + + public JspAttribute[] getJspAttributes() { + return jspAttrs; + } + + /* + * Sets the XML-style 'name' attribute + */ + public void setNameAttribute(JspAttribute nameAttr) { + this.nameAttr = nameAttr; + } + + /* + * Gets the XML-style 'name' attribute + */ + public JspAttribute getNameAttribute() { + return this.nameAttr; + } + } + + /** + * Represents a . + */ + public static class JspOutput extends Node { + + public JspOutput(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + super(qName, OUTPUT_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Collected information about child elements. Used by nodes like CustomTag, + * JspBody, and NamedAttribute. The information is set in the Collector. + */ + public static class ChildInfo { + private boolean scriptless; // true if the tag and its body + + // contain no scripting elements. + private boolean hasUseBean; + + private boolean hasIncludeAction; + + private boolean hasParamAction; + + private boolean hasSetProperty; + + private boolean hasScriptingVars; + + public void setScriptless(boolean s) { + scriptless = s; + } + + public boolean isScriptless() { + return scriptless; + } + + public void setHasUseBean(boolean u) { + hasUseBean = u; + } + + public boolean hasUseBean() { + return hasUseBean; + } + + public void setHasIncludeAction(boolean i) { + hasIncludeAction = i; + } + + public boolean hasIncludeAction() { + return hasIncludeAction; + } + + public void setHasParamAction(boolean i) { + hasParamAction = i; + } + + public boolean hasParamAction() { + return hasParamAction; + } + + public void setHasSetProperty(boolean s) { + hasSetProperty = s; + } + + public boolean hasSetProperty() { + return hasSetProperty; + } + + public void setHasScriptingVars(boolean s) { + hasScriptingVars = s; + } + + public boolean hasScriptingVars() { + return hasScriptingVars; + } + } + + /** + * Represents a custom tag + */ + public static class CustomTag extends Node { + + private String uri; + + private String prefix; + + private JspAttribute[] jspAttrs; + + private TagData tagData; + + private String tagHandlerPoolName; + + private TagInfo tagInfo; + + private TagFileInfo tagFileInfo; + + private Class tagHandlerClass; + + private VariableInfo[] varInfos; + + private int customNestingLevel; + + private ChildInfo childInfo; + + private boolean implementsIterationTag; + + private boolean implementsBodyTag; + + private boolean implementsTryCatchFinally; + + private boolean implementsJspIdConsumer; + + private boolean implementsSimpleTag; + + private boolean implementsDynamicAttributes; + + private Vector atBeginScriptingVars; + + private Vector atEndScriptingVars; + + private Vector nestedScriptingVars; + + private Node.CustomTag customTagParent; + + private Integer numCount; + + private boolean useTagPlugin; + + private TagPluginContext tagPluginContext; + + /** + * The following two fields are used for holding the Java scriptlets + * that the tag plugins may generate. Meaningful only if useTagPlugin is + * true; Could move them into TagPluginContextImpl, but we'll need to + * cast tagPluginContext to TagPluginContextImpl all the time... + */ + private Nodes atSTag; + + private Nodes atETag; + + /* + * Constructor for custom action implemented by tag handler. + */ + public CustomTag(String qName, String prefix, String localName, + String uri, Attributes attrs, Mark start, Node parent, + TagInfo tagInfo, Class tagHandlerClass) { + this(qName, prefix, localName, uri, attrs, null, null, start, + parent, tagInfo, tagHandlerClass); + } + + /* + * Constructor for custom action implemented by tag handler. + */ + public CustomTag(String qName, String prefix, String localName, + String uri, Attributes attrs, Attributes nonTaglibXmlnsAttrs, + Attributes taglibAttrs, Mark start, Node parent, + TagInfo tagInfo, Class tagHandlerClass) { + super(qName, localName, attrs, nonTaglibXmlnsAttrs, taglibAttrs, + start, parent); + + this.uri = uri; + this.prefix = prefix; + this.tagInfo = tagInfo; + this.tagHandlerClass = tagHandlerClass; + this.customNestingLevel = makeCustomNestingLevel(); + this.childInfo = new ChildInfo(); + + this.implementsIterationTag = IterationTag.class + .isAssignableFrom(tagHandlerClass); + this.implementsBodyTag = BodyTag.class + .isAssignableFrom(tagHandlerClass); + this.implementsTryCatchFinally = TryCatchFinally.class + .isAssignableFrom(tagHandlerClass); + this.implementsSimpleTag = SimpleTag.class + .isAssignableFrom(tagHandlerClass); + this.implementsDynamicAttributes = DynamicAttributes.class + .isAssignableFrom(tagHandlerClass); + this.implementsJspIdConsumer = JspIdConsumer.class + .isAssignableFrom(tagHandlerClass); + } + + /* + * Constructor for custom action implemented by tag file. + */ + public CustomTag(String qName, String prefix, String localName, + String uri, Attributes attrs, Mark start, Node parent, + TagFileInfo tagFileInfo) { + this(qName, prefix, localName, uri, attrs, null, null, start, + parent, tagFileInfo); + } + + /* + * Constructor for custom action implemented by tag file. + */ + public CustomTag(String qName, String prefix, String localName, + String uri, Attributes attrs, Attributes nonTaglibXmlnsAttrs, + Attributes taglibAttrs, Mark start, Node parent, + TagFileInfo tagFileInfo) { + + super(qName, localName, attrs, nonTaglibXmlnsAttrs, taglibAttrs, + start, parent); + + this.uri = uri; + this.prefix = prefix; + this.tagFileInfo = tagFileInfo; + this.tagInfo = tagFileInfo.getTagInfo(); + this.customNestingLevel = makeCustomNestingLevel(); + this.childInfo = new ChildInfo(); + + this.implementsIterationTag = false; + this.implementsBodyTag = false; + this.implementsTryCatchFinally = false; + this.implementsSimpleTag = true; + this.implementsJspIdConsumer = false; + this.implementsDynamicAttributes = tagInfo.hasDynamicAttributes(); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + /** + * @return The URI namespace that this custom action belongs to + */ + public String getURI() { + return this.uri; + } + + /** + * @return The tag prefix + */ + public String getPrefix() { + return prefix; + } + + public void setJspAttributes(JspAttribute[] jspAttrs) { + this.jspAttrs = jspAttrs; + } + + public TagAttributeInfo getTagAttributeInfo(String name) { + TagInfo info = this.getTagInfo(); + if (info == null) + return null; + TagAttributeInfo[] tai = info.getAttributes(); + for (int i = 0; i < tai.length; i++) { + if (tai[i].getName().equals(name)) { + return tai[i]; + } + } + return null; + } + + public JspAttribute[] getJspAttributes() { + return jspAttrs; + } + + public ChildInfo getChildInfo() { + return childInfo; + } + + public void setTagData(TagData tagData) { + this.tagData = tagData; + this.varInfos = tagInfo.getVariableInfo(tagData); + if (this.varInfos == null) { + this.varInfos = ZERO_VARIABLE_INFO; + } + } + + public TagData getTagData() { + return tagData; + } + + public void setTagHandlerPoolName(String s) { + tagHandlerPoolName = s; + } + + public String getTagHandlerPoolName() { + return tagHandlerPoolName; + } + + public TagInfo getTagInfo() { + return tagInfo; + } + + public TagFileInfo getTagFileInfo() { + return tagFileInfo; + } + + /* + * @return true if this custom action is supported by a tag file, false + * otherwise + */ + public boolean isTagFile() { + return tagFileInfo != null; + } + + public Class getTagHandlerClass() { + return tagHandlerClass; + } + + public void setTagHandlerClass(Class hc) { + tagHandlerClass = hc; + } + + public boolean implementsIterationTag() { + return implementsIterationTag; + } + + public boolean implementsBodyTag() { + return implementsBodyTag; + } + + public boolean implementsTryCatchFinally() { + return implementsTryCatchFinally; + } + + public boolean implementsJspIdConsumer() { + return implementsJspIdConsumer; + } + + public boolean implementsSimpleTag() { + return implementsSimpleTag; + } + + public boolean implementsDynamicAttributes() { + return implementsDynamicAttributes; + } + + public TagVariableInfo[] getTagVariableInfos() { + return tagInfo.getTagVariableInfos(); + } + + public VariableInfo[] getVariableInfos() { + return varInfos; + } + + public void setCustomTagParent(Node.CustomTag n) { + this.customTagParent = n; + } + + public Node.CustomTag getCustomTagParent() { + return this.customTagParent; + } + + public void setNumCount(Integer count) { + this.numCount = count; + } + + public Integer getNumCount() { + return this.numCount; + } + + public void setScriptingVars(Vector vec, int scope) { + switch (scope) { + case VariableInfo.AT_BEGIN: + this.atBeginScriptingVars = vec; + break; + case VariableInfo.AT_END: + this.atEndScriptingVars = vec; + break; + case VariableInfo.NESTED: + this.nestedScriptingVars = vec; + break; + } + } + + /* + * Gets the scripting variables for the given scope that need to be + * declared. + */ + public Vector getScriptingVars(int scope) { + Vector vec = null; + + switch (scope) { + case VariableInfo.AT_BEGIN: + vec = this.atBeginScriptingVars; + break; + case VariableInfo.AT_END: + vec = this.atEndScriptingVars; + break; + case VariableInfo.NESTED: + vec = this.nestedScriptingVars; + break; + } + + return vec; + } + + /* + * Gets this custom tag's custom nesting level, which is given as the + * number of times this custom tag is nested inside itself. + */ + public int getCustomNestingLevel() { + return customNestingLevel; + } + + /** + * Checks to see if the attribute of the given name is of type + * JspFragment. + */ + public boolean checkIfAttributeIsJspFragment(String name) { + boolean result = false; + + TagAttributeInfo[] attributes = tagInfo.getAttributes(); + for (int i = 0; i < attributes.length; i++) { + if (attributes[i].getName().equals(name) + && attributes[i].isFragment()) { + result = true; + break; + } + } + + return result; + } + + public void setUseTagPlugin(boolean use) { + useTagPlugin = use; + } + + public boolean useTagPlugin() { + return useTagPlugin; + } + + public void setTagPluginContext(TagPluginContext tagPluginContext) { + this.tagPluginContext = tagPluginContext; + } + + public TagPluginContext getTagPluginContext() { + return tagPluginContext; + } + + public void setAtSTag(Nodes sTag) { + atSTag = sTag; + } + + public Nodes getAtSTag() { + return atSTag; + } + + public void setAtETag(Nodes eTag) { + atETag = eTag; + } + + public Nodes getAtETag() { + return atETag; + } + + /* + * Computes this custom tag's custom nesting level, which corresponds to + * the number of times this custom tag is nested inside itself. + * + * Example: + * + * -- nesting level 0 -- nesting level 1 + * -- nesting level 2 -- nesting level 1 + * + * + * @return Custom tag's nesting level + */ + private int makeCustomNestingLevel() { + int n = 0; + Node p = parent; + while (p != null) { + if ((p instanceof Node.CustomTag) + && qName.equals(((Node.CustomTag) p).qName)) { + n++; + } + p = p.parent; + } + return n; + } + + /** + * Returns true if this custom action has an empty body, and false + * otherwise. + * + * A custom action is considered to have an empty body if the following + * holds true: - getBody() returns null, or - all immediate children are + * jsp:attribute actions, or - the action's jsp:body is empty. + */ + public boolean hasEmptyBody() { + boolean hasEmptyBody = true; + Nodes nodes = getBody(); + if (nodes != null) { + int numChildNodes = nodes.size(); + for (int i = 0; i < numChildNodes; i++) { + Node n = nodes.getNode(i); + if (!(n instanceof NamedAttribute)) { + if (n instanceof JspBody) { + hasEmptyBody = (n.getBody() == null); + } else { + hasEmptyBody = false; + } + break; + } + } + } + + return hasEmptyBody; + } + } + + /** + * Used as a placeholder for the evaluation code of a custom action + * attribute (used by the tag plugin machinery only). + */ + public static class AttributeGenerator extends Node { + String name; // name of the attribute + + CustomTag tag; // The tag this attribute belongs to + + public AttributeGenerator(Mark start, String name, CustomTag tag) { + super(start, null); + this.name = name; + this.tag = tag; + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public String getName() { + return name; + } + + public CustomTag getTag() { + return tag; + } + } + + /** + * Represents the body of a <jsp:text> element + */ + public static class JspText extends Node { + + public JspText(String qName, Attributes nonTaglibXmlnsAttrs, + Attributes taglibAttrs, Mark start, Node parent) { + super(qName, TEXT_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs, + start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + } + + /** + * Represents a Named Attribute (<jsp:attribute>) + */ + public static class NamedAttribute extends Node { + + // A unique temporary variable name suitable for code generation + private String temporaryVariableName; + + // True if this node is to be trimmed, or false otherwise + private boolean trim = true; + + private ChildInfo childInfo; + + private String name; + + private String localName; + + private String prefix; + + public NamedAttribute(Attributes attrs, Mark start, Node parent) { + this(JSP_ATTRIBUTE_ACTION, attrs, null, null, start, parent); + } + + public NamedAttribute(String qName, Attributes attrs, + Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, + Mark start, Node parent) { + + super(qName, ATTRIBUTE_ACTION, attrs, nonTaglibXmlnsAttrs, + taglibAttrs, start, parent); + if ("false".equals(this.getAttributeValue("trim"))) { + // (if null or true, leave default of true) + trim = false; + } + childInfo = new ChildInfo(); + name = this.getAttributeValue("name"); + if (name != null) { + // Mandatary attribute "name" will be checked in Validator + localName = name; + int index = name.indexOf(':'); + if (index != -1) { + prefix = name.substring(0, index); + localName = name.substring(index + 1); + } + } + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public String getName() { + return this.name; + } + + public String getLocalName() { + return this.localName; + } + + public String getPrefix() { + return this.prefix; + } + + public ChildInfo getChildInfo() { + return this.childInfo; + } + + public boolean isTrim() { + return trim; + } + + /** + * @return A unique temporary variable name to store the result in. + * (this probably could go elsewhere, but it's convenient here) + */ + public String getTemporaryVariableName() { + if (temporaryVariableName == null) { + temporaryVariableName = getRoot().nextTemporaryVariableName(); + } + return temporaryVariableName; + } + + /* + * Get the attribute value from this named attribute (). + * Since this method is only for attributes that are not rtexpr, we can + * assume the body of the jsp:attribute is a template text. + */ + public String getText() { + + class AttributeVisitor extends Visitor { + String attrValue = null; + + public void visit(TemplateText txt) { + attrValue = new String(txt.getText()); + } + + public String getAttrValue() { + return attrValue; + } + } + + // According to JSP 2.0, if the body of the + // action is empty, it is equivalent of specifying "" as the value + // of the attribute. + String text = ""; + if (getBody() != null) { + AttributeVisitor attributeVisitor = new AttributeVisitor(); + try { + getBody().visit(attributeVisitor); + } catch (JasperException e) { + } + text = attributeVisitor.getAttrValue(); + } + + return text; + } + } + + /** + * Represents a JspBody node (<jsp:body>) + */ + public static class JspBody extends Node { + + private ChildInfo childInfo; + + public JspBody(Mark start, Node parent) { + this(JSP_BODY_ACTION, null, null, start, parent); + } + + public JspBody(String qName, Attributes nonTaglibXmlnsAttrs, + Attributes taglibAttrs, Mark start, Node parent) { + super(qName, BODY_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs, + start, parent); + this.childInfo = new ChildInfo(); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + public ChildInfo getChildInfo() { + return childInfo; + } + } + + /** + * Represents a template text string + */ + public static class TemplateText extends Node { + + private ArrayList extraSmap = null; + + public TemplateText(String text, Mark start, Node parent) { + super(null, null, text, start, parent); + } + + public void accept(Visitor v) throws JasperException { + v.visit(this); + } + + /** + * Trim all whitespace from the left of the template text + */ + public void ltrim() { + int index = 0; + while ((index < text.length()) && (text.charAt(index) <= ' ')) { + index++; + } + text = text.substring(index); + } + + public void setText(String text) { + this.text = text; + } + + /** + * Trim all whitespace from the right of the template text + */ + public void rtrim() { + int index = text.length(); + while ((index > 0) && (text.charAt(index - 1) <= ' ')) { + index--; + } + text = text.substring(0, index); + } + + /** + * Returns true if this template text contains whitespace only. + */ + public boolean isAllSpace() { + boolean isAllSpace = true; + for (int i = 0; i < text.length(); i++) { + if (!Character.isWhitespace(text.charAt(i))) { + isAllSpace = false; + break; + } + } + return isAllSpace; + } + + /** + * Add a source to Java line mapping + * + * @param srcLine + * The postion of the source line, relative to the line at + * the start of this node. The corresponding java line is + * assumed to be consecutive, i.e. one more than the last. + */ + public void addSmap(int srcLine) { + if (extraSmap == null) { + extraSmap = new ArrayList(); + } + extraSmap.add(new Integer(srcLine)); + } + + public ArrayList getExtraSmap() { + return extraSmap; + } + } + + /*************************************************************************** + * Auxillary classes used in Node + */ + + /** + * Represents attributes that can be request time expressions. + * + * Can either be a plain attribute, an attribute that represents a request + * time expression value, or a named attribute (specified using the + * jsp:attribute standard action). + */ + + public static class JspAttribute { + + private String qName; + + private String uri; + + private String localName; + + private String value; + + private boolean expression; + + private boolean dynamic; + + private final ELNode.Nodes el; + + private final TagAttributeInfo tai; + + // If true, this JspAttribute represents a + private boolean namedAttribute; + + // The node in the parse tree for the NamedAttribute + private NamedAttribute namedAttributeNode; + + JspAttribute(TagAttributeInfo tai, String qName, String uri, + String localName, String value, boolean expr, ELNode.Nodes el, + boolean dyn) { + this.qName = qName; + this.uri = uri; + this.localName = localName; + this.value = value; + this.namedAttributeNode = null; + this.expression = expr; + this.el = el; + this.dynamic = dyn; + this.namedAttribute = false; + this.tai = tai; + } + + /** + * Allow node to validate itself + * + * @param ef + * @param ctx + * @throws ELException + */ + public void validateEL(ExpressionFactory ef, ELContext ctx) + throws ELException { + if (this.el != null) { + // determine exact type + ValueExpression ve = ef.createValueExpression(ctx, this.value, + String.class); + } + } + + /** + * Use this constructor if the JspAttribute represents a named + * attribute. In this case, we have to store the nodes of the body of + * the attribute. + */ + JspAttribute(NamedAttribute na, TagAttributeInfo tai, boolean dyn) { + this.qName = na.getName(); + this.localName = na.getLocalName(); + this.value = null; + this.namedAttributeNode = na; + this.expression = false; + this.el = null; + this.dynamic = dyn; + this.namedAttribute = true; + this.tai = null; + } + + /** + * @return The name of the attribute + */ + public String getName() { + return qName; + } + + /** + * @return The local name of the attribute + */ + public String getLocalName() { + return localName; + } + + /** + * @return The namespace of the attribute, or null if in the default + * namespace + */ + public String getURI() { + return uri; + } + + public TagAttributeInfo getTagAttributeInfo() { + return this.tai; + } + + /** + * + * @return return true if there's TagAttributeInfo meaning we need to + * assign a ValueExpression + */ + public boolean isDeferredInput() { + return (this.tai != null) ? this.tai.isDeferredValue() : false; + } + + /** + * + * @return return true if there's TagAttributeInfo meaning we need to + * assign a MethodExpression + */ + public boolean isDeferredMethodInput() { + return (this.tai != null) ? this.tai.isDeferredMethod() : false; + } + + public String getExpectedTypeName() { + if (this.tai != null) { + if (this.isDeferredInput()) { + return this.tai.getExpectedTypeName(); + } else if (this.isDeferredMethodInput()) { + String m = this.tai.getMethodSignature(); + if (m != null) { + int rti = m.trim().indexOf(' '); + if (rti > 0) { + return m.substring(0, rti).trim(); + } + } + } + } + return "java.lang.Object"; + } + + public String[] getParameterTypeNames() { + if (this.tai != null) { + if (this.isDeferredMethodInput()) { + String m = this.tai.getMethodSignature(); + if (m != null) { + m = m.trim(); + m = m.substring(m.indexOf('(') + 1); + m = m.substring(0, m.length() - 1); + if (m.trim().length() > 0) { + String[] p = m.split(","); + for (int i = 0; i < p.length; i++) { + p[i] = p[i].trim(); + } + return p; + } + } + } + } + return new String[0]; + } + + /** + * Only makes sense if namedAttribute is false. + * + * @return the value for the attribute, or the expression string + * (stripped of "<%=", "%>", "%=", or "%" but containing "${" + * and "}" for EL expressions) + */ + public String getValue() { + return value; + } + + /** + * Only makes sense if namedAttribute is true. + * + * @return the nodes that evaluate to the body of this attribute. + */ + public NamedAttribute getNamedAttributeNode() { + return namedAttributeNode; + } + + /** + * @return true if the value represents a traditional rtexprvalue + */ + public boolean isExpression() { + return expression; + } + + /** + * @return true if the value represents a NamedAttribute value. + */ + public boolean isNamedAttribute() { + return namedAttribute; + } + + /** + * @return true if the value represents an expression that should be fed + * to the expression interpreter + * @return false for string literals or rtexprvalues that should not be + * interpreted or reevaluated + */ + public boolean isELInterpreterInput() { + return el != null || this.isDeferredInput() + || this.isDeferredMethodInput(); + } + + /** + * @return true if the value is a string literal known at translation + * time. + */ + public boolean isLiteral() { + return !expression && (el != null) && !namedAttribute; + } + + /** + * XXX + */ + public boolean isDynamic() { + return dynamic; + } + + public ELNode.Nodes getEL() { + return el; + } + } + + /** + * An ordered list of Node, used to represent the body of an element, or a + * jsp page of jsp document. + */ + public static class Nodes { + + private List list; + + private Node.Root root; // null if this is not a page + + private boolean generatedInBuffer; + + public Nodes() { + list = new Vector(); + } + + public Nodes(Node.Root root) { + this.root = root; + list = new Vector(); + list.add(root); + } + + /** + * Appends a node to the list + * + * @param n + * The node to add + */ + public void add(Node n) { + list.add(n); + root = null; + } + + /** + * Removes the given node from the list. + * + * @param n + * The node to be removed + */ + public void remove(Node n) { + list.remove(n); + } + + /** + * Visit the nodes in the list with the supplied visitor + * + * @param v + * The visitor used + */ + public void visit(Visitor v) throws JasperException { + Iterator iter = list.iterator(); + while (iter.hasNext()) { + Node n = (Node) iter.next(); + n.accept(v); + } + } + + public int size() { + return list.size(); + } + + public Node getNode(int index) { + Node n = null; + try { + n = (Node) list.get(index); + } catch (ArrayIndexOutOfBoundsException e) { + } + return n; + } + + public Node.Root getRoot() { + return root; + } + + public boolean isGeneratedInBuffer() { + return generatedInBuffer; + } + + public void setGeneratedInBuffer(boolean g) { + generatedInBuffer = g; + } + } + + /** + * A visitor class for visiting the node. This class also provides the + * default action (i.e. nop) for each of the child class of the Node. An + * actual visitor should extend this class and supply the visit method for + * the nodes that it cares. + */ + public static class Visitor { + + /** + * This method provides a place to put actions that are common to all + * nodes. Override this in the child visitor class if need to. + */ + protected void doVisit(Node n) throws JasperException { + } + + /** + * Visit the body of a node, using the current visitor + */ + protected void visitBody(Node n) throws JasperException { + if (n.getBody() != null) { + n.getBody().visit(this); + } + } + + public void visit(Root n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(JspRoot n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(PageDirective n) throws JasperException { + doVisit(n); + } + + public void visit(TagDirective n) throws JasperException { + doVisit(n); + } + + public void visit(IncludeDirective n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(TaglibDirective n) throws JasperException { + doVisit(n); + } + + public void visit(AttributeDirective n) throws JasperException { + doVisit(n); + } + + public void visit(VariableDirective n) throws JasperException { + doVisit(n); + } + + public void visit(Comment n) throws JasperException { + doVisit(n); + } + + public void visit(Declaration n) throws JasperException { + doVisit(n); + } + + public void visit(Expression n) throws JasperException { + doVisit(n); + } + + public void visit(Scriptlet n) throws JasperException { + doVisit(n); + } + + public void visit(ELExpression n) throws JasperException { + doVisit(n); + } + + public void visit(IncludeAction n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(ForwardAction n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(GetProperty n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(SetProperty n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(ParamAction n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(ParamsAction n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(FallBackAction n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(UseBean n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(PlugIn n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(CustomTag n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(UninterpretedTag n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(JspElement n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(JspText n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(NamedAttribute n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(JspBody n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(InvokeAction n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(DoBodyAction n) throws JasperException { + doVisit(n); + visitBody(n); + } + + public void visit(TemplateText n) throws JasperException { + doVisit(n); + } + + public void visit(JspOutput n) throws JasperException { + doVisit(n); + } + + public void visit(AttributeGenerator n) throws JasperException { + doVisit(n); + } + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/PageDataImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/PageDataImpl.java new file mode 100644 index 000000000..deb4adfe2 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/PageDataImpl.java @@ -0,0 +1,711 @@ +/* + * 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.InputStream; +import java.io.ByteArrayInputStream; +import java.io.CharArrayWriter; +import java.io.UnsupportedEncodingException; +import java.util.ListIterator; +import javax.servlet.jsp.tagext.PageData; +import org.xml.sax.Attributes; +import org.xml.sax.helpers.AttributesImpl; +import org.apache.jasper.JasperException; + +/** + * An implementation of javax.servlet.jsp.tagext.PageData which + * builds the XML view of a given page. + * + * The XML view is built in two passes: + * + * During the first pass, the FirstPassVisitor collects the attributes of the + * top-level jsp:root and those of the jsp:root elements of any included + * pages, and adds them to the jsp:root element of the XML view. + * In addition, any taglib directives are converted into xmlns: attributes and + * added to the jsp:root element of the XML view. + * This pass ignores any nodes other than JspRoot and TaglibDirective. + * + * During the second pass, the SecondPassVisitor produces the XML view, using + * the combined jsp:root attributes determined in the first pass and any + * remaining pages nodes (this pass ignores any JspRoot and TaglibDirective + * nodes). + * + * @author Jan Luehe + */ +class PageDataImpl extends PageData implements TagConstants { + + private static final String JSP_VERSION = "2.0"; + private static final String CDATA_START_SECTION = "\n"; + + // string buffer used to build XML view + private StringBuffer buf; + + /** + * Constructor. + * + * @param page the page nodes from which to generate the XML view + */ + public PageDataImpl(Node.Nodes page, Compiler compiler) + throws JasperException { + + // First pass + FirstPassVisitor firstPass = new FirstPassVisitor(page.getRoot(), + compiler.getPageInfo()); + page.visit(firstPass); + + // Second pass + buf = new StringBuffer(); + SecondPassVisitor secondPass + = new SecondPassVisitor(page.getRoot(), buf, compiler, + firstPass.getJspIdPrefix()); + page.visit(secondPass); + } + + /** + * Returns the input stream of the XML view. + * + * @return the input stream of the XML view + */ + public InputStream getInputStream() { + // Turn StringBuffer into InputStream + try { + return new ByteArrayInputStream(buf.toString().getBytes("UTF-8")); + } catch (UnsupportedEncodingException uee) { + // should never happen + throw new RuntimeException(uee.toString()); + } + } + + /* + * First-pass Visitor for JspRoot nodes (representing jsp:root elements) + * and TablibDirective nodes, ignoring any other nodes. + * + * The purpose of this Visitor is to collect the attributes of the + * top-level jsp:root and those of the jsp:root elements of any included + * pages, and add them to the jsp:root element of the XML view. + * In addition, this Visitor converts any taglib directives into xmlns: + * attributes and adds them to the jsp:root element of the XML view. + */ + static class FirstPassVisitor + extends Node.Visitor implements TagConstants { + + private Node.Root root; + private AttributesImpl rootAttrs; + private PageInfo pageInfo; + + // Prefix for the 'id' attribute + private String jspIdPrefix; + + /* + * Constructor + */ + public FirstPassVisitor(Node.Root root, PageInfo pageInfo) { + this.root = root; + this.pageInfo = pageInfo; + this.rootAttrs = new AttributesImpl(); + this.rootAttrs.addAttribute("", "", "version", "CDATA", + JSP_VERSION); + this.jspIdPrefix = "jsp"; + } + + public void visit(Node.Root n) throws JasperException { + visitBody(n); + if (n == root) { + /* + * Top-level page. + * + * Add + * xmlns:jsp="http://java.sun.com/JSP/Page" + * attribute only if not already present. + */ + if (!JSP_URI.equals(rootAttrs.getValue("xmlns:jsp"))) { + rootAttrs.addAttribute("", "", "xmlns:jsp", "CDATA", + JSP_URI); + } + + if (pageInfo.isJspPrefixHijacked()) { + /* + * 'jsp' prefix has been hijacked, that is, bound to a + * namespace other than the JSP namespace. This means that + * when adding an 'id' attribute to each element, we can't + * use the 'jsp' prefix. Therefore, create a new prefix + * (one that is unique across the translation unit) for use + * by the 'id' attribute, and bind it to the JSP namespace + */ + jspIdPrefix += "jsp"; + while (pageInfo.containsPrefix(jspIdPrefix)) { + jspIdPrefix += "jsp"; + } + rootAttrs.addAttribute("", "", "xmlns:" + jspIdPrefix, + "CDATA", JSP_URI); + } + + root.setAttributes(rootAttrs); + } + } + + public void visit(Node.JspRoot n) throws JasperException { + addAttributes(n.getTaglibAttributes()); + addAttributes(n.getNonTaglibXmlnsAttributes()); + addAttributes(n.getAttributes()); + + visitBody(n); + } + + /* + * Converts taglib directive into "xmlns:..." attribute of jsp:root + * element. + */ + public void visit(Node.TaglibDirective n) throws JasperException { + Attributes attrs = n.getAttributes(); + if (attrs != null) { + String qName = "xmlns:" + attrs.getValue("prefix"); + /* + * According to javadocs of org.xml.sax.helpers.AttributesImpl, + * the addAttribute method does not check to see if the + * specified attribute is already contained in the list: This + * is the application's responsibility! + */ + if (rootAttrs.getIndex(qName) == -1) { + String location = attrs.getValue("uri"); + if (location != null) { + if (location.startsWith("/")) { + location = URN_JSPTLD + location; + } + rootAttrs.addAttribute("", "", qName, "CDATA", + location); + } else { + location = attrs.getValue("tagdir"); + rootAttrs.addAttribute("", "", qName, "CDATA", + URN_JSPTAGDIR + location); + } + } + } + } + + public String getJspIdPrefix() { + return jspIdPrefix; + } + + private void addAttributes(Attributes attrs) { + if (attrs != null) { + int len = attrs.getLength(); + + for (int i=0; i"); + } + buf.append("${"); + buf.append(JspUtil.escapeXml(n.getText())); + buf.append("}"); + if (!n.getRoot().isXmlSyntax()) { + buf.append(JSP_TEXT_ACTION_END); + } + buf.append("\n"); + } + + public void visit(Node.IncludeAction n) throws JasperException { + appendTag(n); + } + + public void visit(Node.ForwardAction n) throws JasperException { + appendTag(n); + } + + public void visit(Node.GetProperty n) throws JasperException { + appendTag(n); + } + + public void visit(Node.SetProperty n) throws JasperException { + appendTag(n); + } + + public void visit(Node.ParamAction n) throws JasperException { + appendTag(n); + } + + public void visit(Node.ParamsAction n) throws JasperException { + appendTag(n); + } + + public void visit(Node.FallBackAction n) throws JasperException { + appendTag(n); + } + + public void visit(Node.UseBean n) throws JasperException { + appendTag(n); + } + + public void visit(Node.PlugIn n) throws JasperException { + appendTag(n); + } + + public void visit(Node.NamedAttribute n) throws JasperException { + appendTag(n); + } + + public void visit(Node.JspBody n) throws JasperException { + appendTag(n); + } + + public void visit(Node.CustomTag n) throws JasperException { + boolean resetDefaultNSSave = resetDefaultNS; + appendTag(n, resetDefaultNS); + resetDefaultNS = resetDefaultNSSave; + } + + public void visit(Node.UninterpretedTag n) throws JasperException { + boolean resetDefaultNSSave = resetDefaultNS; + appendTag(n, resetDefaultNS); + resetDefaultNS = resetDefaultNSSave; + } + + public void visit(Node.JspText n) throws JasperException { + appendTag(n); + } + + public void visit(Node.DoBodyAction n) throws JasperException { + appendTag(n); + } + + public void visit(Node.InvokeAction n) throws JasperException { + appendTag(n); + } + + public void visit(Node.TagDirective n) throws JasperException { + appendTagDirective(n); + } + + public void visit(Node.AttributeDirective n) throws JasperException { + appendTag(n); + } + + public void visit(Node.VariableDirective n) throws JasperException { + appendTag(n); + } + + public void visit(Node.TemplateText n) throws JasperException { + /* + * If the template text came from a JSP page written in JSP syntax, + * create a jsp:text element for it (JSP 5.3.2). + */ + appendText(n.getText(), !n.getRoot().isXmlSyntax()); + } + + /* + * Appends the given tag, including its body, to the XML view. + */ + private void appendTag(Node n) throws JasperException { + appendTag(n, false); + } + + /* + * Appends the given tag, including its body, to the XML view, + * and optionally reset default namespace to "", if none specified. + */ + private void appendTag(Node n, boolean addDefaultNS) + throws JasperException { + + Node.Nodes body = n.getBody(); + String text = n.getText(); + + buf.append("<").append(n.getQName()); + buf.append("\n"); + + printAttributes(n, addDefaultNS); + buf.append(" ").append(jspIdPrefix).append(":id").append("=\""); + buf.append(jspId++).append("\"\n"); + + if (ROOT_ACTION.equals(n.getLocalName()) || body != null + || text != null) { + buf.append(">\n"); + if (ROOT_ACTION.equals(n.getLocalName())) { + if (compiler.getCompilationContext().isTagFile()) { + appendTagDirective(); + } else { + appendPageDirective(); + } + } + if (body != null) { + body.visit(this); + } else { + appendText(text, false); + } + buf.append("\n"); + } else { + buf.append("/>\n"); + } + } + + /* + * Appends the page directive with the given attributes to the XML + * view. + * + * Since the import attribute of the page directive is the only page + * attribute that is allowed to appear multiple times within the same + * document, and since XML allows only single-value attributes, + * the values of multiple import attributes must be combined into one, + * separated by comma. + * + * If the given page directive contains just 'contentType' and/or + * 'pageEncoding' attributes, we ignore it, as we've already appended + * a page directive containing just these two attributes. + */ + private void appendPageDirective(Node.PageDirective n) { + boolean append = false; + Attributes attrs = n.getAttributes(); + int len = (attrs == null) ? 0 : attrs.getLength(); + for (int i=0; i 0) { + // Concatenate names of imported classes/packages + boolean first = true; + ListIterator iter = n.getImports().listIterator(); + while (iter.hasNext()) { + if (first) { + first = false; + buf.append(" import=\""); + } else { + buf.append(","); + } + buf.append(JspUtil.getExprInXml((String) iter.next())); + } + buf.append("\"\n"); + } + buf.append("/>\n"); + } + + /* + * Appends a page directive with 'pageEncoding' and 'contentType' + * attributes. + * + * The value of the 'pageEncoding' attribute is hard-coded + * to UTF-8, whereas the value of the 'contentType' attribute, which + * is identical to what the container will pass to + * ServletResponse.setContentType(), is derived from the pageInfo. + */ + private void appendPageDirective() { + buf.append("<").append(JSP_PAGE_DIRECTIVE_ACTION); + buf.append("\n"); + + // append jsp:id + buf.append(" ").append(jspIdPrefix).append(":id").append("=\""); + buf.append(jspId++).append("\"\n"); + buf.append(" ").append("pageEncoding").append("=\"UTF-8\"\n"); + buf.append(" ").append("contentType").append("=\""); + buf.append(compiler.getPageInfo().getContentType()).append("\"\n"); + buf.append("/>\n"); + } + + /* + * Appends the tag directive with the given attributes to the XML + * view. + * + * If the given tag directive contains just a 'pageEncoding' + * attributes, we ignore it, as we've already appended + * a tag directive containing just this attributes. + */ + private void appendTagDirective(Node.TagDirective n) + throws JasperException { + + boolean append = false; + Attributes attrs = n.getAttributes(); + int len = (attrs == null) ? 0 : attrs.getLength(); + for (int i=0; i\n"); + } + + private void appendText(String text, boolean createJspTextElement) { + if (createJspTextElement) { + buf.append("<").append(JSP_TEXT_ACTION); + buf.append("\n"); + + // append jsp:id + buf.append(" ").append(jspIdPrefix).append(":id").append("=\""); + buf.append(jspId++).append("\"\n"); + buf.append(">\n"); + + appendCDATA(text); + buf.append(JSP_TEXT_ACTION_END); + buf.append("\n"); + } else { + appendCDATA(text); + } + } + + /* + * Appends the given text as a CDATA section to the XML view, unless + * the text has already been marked as CDATA. + */ + private void appendCDATA(String text) { + buf.append(CDATA_START_SECTION); + buf.append(escapeCDATA(text)); + buf.append(CDATA_END_SECTION); + } + + /* + * Escapes any occurrences of "]]>" (by replacing them with "]]>") + * within the given text, so it can be included in a CDATA section. + */ + private String escapeCDATA(String text) { + if( text==null ) return ""; + int len = text.length(); + CharArrayWriter result = new CharArrayWriter(len); + for (int i=0; i')) { + // match found + result.write(']'); + result.write(']'); + result.write('&'); + result.write('g'); + result.write('t'); + result.write(';'); + i += 2; + } else { + result.write(text.charAt(i)); + } + } + return result.toString(); + } + + /* + * Appends the attributes of the given Node to the XML view. + */ + private void printAttributes(Node n, boolean addDefaultNS) { + + /* + * Append "xmlns" attributes that represent tag libraries + */ + Attributes attrs = n.getTaglibAttributes(); + int len = (attrs == null) ? 0 : attrs.getLength(); + for (int i=0; i\n"); + } + } +} + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/PageInfo.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/PageInfo.java new file mode 100644 index 000000000..5a016fc39 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/PageInfo.java @@ -0,0 +1,711 @@ +/* + * 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.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Vector; + +import org.apache.el.ExpressionFactoryImpl; +import org.apache.jasper.Constants; +import org.apache.jasper.JasperException; + +import javax.el.ExpressionFactory; +import javax.servlet.jsp.tagext.TagLibraryInfo; + +/** + * A repository for various info about the translation unit under compilation. + * + * @author Kin-man Chung + */ + +class PageInfo { + + private Vector imports; + private Vector dependants; + + private BeanRepository beanRepository; + private HashMap taglibsMap; + private HashMap jspPrefixMapper; + private HashMap xmlPrefixMapper; + private HashMap nonCustomTagPrefixMap; + private String jspFile; + private String defaultLanguage = "java"; + private String language; + private String defaultExtends = Constants.JSP_SERVLET_BASE; + private String xtends; + private String contentType = null; + private String session; + private boolean isSession = true; + private String bufferValue; + private int buffer = 8*1024; // XXX confirm + private String autoFlush; + private boolean isAutoFlush = true; + private String isThreadSafeValue; + private boolean isThreadSafe = true; + private String isErrorPageValue; + private boolean isErrorPage = false; + private String errorPage = null; + private String info; + + private boolean scriptless = false; + private boolean scriptingInvalid = false; + + private String isELIgnoredValue; + private boolean isELIgnored = false; + + // JSP 2.1 + private String deferredSyntaxAllowedAsLiteralValue; + private boolean deferredSyntaxAllowedAsLiteral = false; + private ExpressionFactory expressionFactory = new ExpressionFactoryImpl(); + private String trimDirectiveWhitespacesValue; + private boolean trimDirectiveWhitespaces = false; + + private String omitXmlDecl = null; + private String doctypeName = null; + private String doctypePublic = null; + private String doctypeSystem = null; + + private boolean isJspPrefixHijacked; + + // Set of all element and attribute prefixes used in this translation unit + private HashSet prefixes; + + private boolean hasJspRoot = false; + private Vector includePrelude; + private Vector includeCoda; + private Vector pluginDcls; // Id's for tagplugin declarations + + + PageInfo(BeanRepository beanRepository, String jspFile) { + + this.jspFile = jspFile; + this.beanRepository = beanRepository; + this.taglibsMap = new HashMap(); + this.jspPrefixMapper = new HashMap(); + this.xmlPrefixMapper = new HashMap(); + this.nonCustomTagPrefixMap = new HashMap(); + this.imports = new Vector(); + this.dependants = new Vector(); + this.includePrelude = new Vector(); + this.includeCoda = new Vector(); + this.pluginDcls = new Vector(); + this.prefixes = new HashSet(); + + // Enter standard imports + for(int i = 0; i < Constants.STANDARD_IMPORTS.length; i++) + imports.add(Constants.STANDARD_IMPORTS[i]); + } + + /** + * Check if the plugin ID has been previously declared. Make a not + * that this Id is now declared. + * @return true if Id has been declared. + */ + public boolean isPluginDeclared(String id) { + if (pluginDcls.contains(id)) + return true; + pluginDcls.add(id); + return false; + } + + public void addImports(List imports) { + this.imports.addAll(imports); + } + + public void addImport(String imp) { + this.imports.add(imp); + } + + public List getImports() { + return imports; + } + + public String getJspFile() { + return jspFile; + } + + public void addDependant(String d) { + if (!dependants.contains(d) && !jspFile.equals(d)) + dependants.add(d); + } + + public List getDependants() { + return dependants; + } + + public BeanRepository getBeanRepository() { + return beanRepository; + } + + public void setScriptless(boolean s) { + scriptless = s; + } + + public boolean isScriptless() { + return scriptless; + } + + public void setScriptingInvalid(boolean s) { + scriptingInvalid = s; + } + + public boolean isScriptingInvalid() { + return scriptingInvalid; + } + + public List getIncludePrelude() { + return includePrelude; + } + + public void setIncludePrelude(Vector prelude) { + includePrelude = prelude; + } + + public List getIncludeCoda() { + return includeCoda; + } + + public void setIncludeCoda(Vector coda) { + includeCoda = coda; + } + + public void setHasJspRoot(boolean s) { + hasJspRoot = s; + } + + public boolean hasJspRoot() { + return hasJspRoot; + } + + public String getOmitXmlDecl() { + return omitXmlDecl; + } + + public void setOmitXmlDecl(String omit) { + omitXmlDecl = omit; + } + + public String getDoctypeName() { + return doctypeName; + } + + public void setDoctypeName(String doctypeName) { + this.doctypeName = doctypeName; + } + + public String getDoctypeSystem() { + return doctypeSystem; + } + + public void setDoctypeSystem(String doctypeSystem) { + this.doctypeSystem = doctypeSystem; + } + + public String getDoctypePublic() { + return doctypePublic; + } + + public void setDoctypePublic(String doctypePublic) { + this.doctypePublic = doctypePublic; + } + + /* Tag library and XML namespace management methods */ + + public void setIsJspPrefixHijacked(boolean isHijacked) { + isJspPrefixHijacked = isHijacked; + } + + public boolean isJspPrefixHijacked() { + return isJspPrefixHijacked; + } + + /* + * Adds the given prefix to the set of prefixes of this translation unit. + * + * @param prefix The prefix to add + */ + public void addPrefix(String prefix) { + prefixes.add(prefix); + } + + /* + * Checks to see if this translation unit contains the given prefix. + * + * @param prefix The prefix to check + * + * @return true if this translation unit contains the given prefix, false + * otherwise + */ + public boolean containsPrefix(String prefix) { + return prefixes.contains(prefix); + } + + /* + * Maps the given URI to the given tag library. + * + * @param uri The URI to map + * @param info The tag library to be associated with the given URI + */ + public void addTaglib(String uri, TagLibraryInfo info) { + taglibsMap.put(uri, info); + } + + /* + * Gets the tag library corresponding to the given URI. + * + * @return Tag library corresponding to the given URI + */ + public TagLibraryInfo getTaglib(String uri) { + return (TagLibraryInfo) taglibsMap.get(uri); + } + + /* + * Gets the collection of tag libraries that are associated with a URI + * + * @return Collection of tag libraries that are associated with a URI + */ + public Collection getTaglibs() { + return taglibsMap.values(); + } + + /* + * Checks to see if the given URI is mapped to a tag library. + * + * @param uri The URI to map + * + * @return true if the given URI is mapped to a tag library, false + * otherwise + */ + public boolean hasTaglib(String uri) { + return taglibsMap.containsKey(uri); + } + + /* + * Maps the given prefix to the given URI. + * + * @param prefix The prefix to map + * @param uri The URI to be associated with the given prefix + */ + public void addPrefixMapping(String prefix, String uri) { + jspPrefixMapper.put(prefix, uri); + } + + /* + * Pushes the given URI onto the stack of URIs to which the given prefix + * is mapped. + * + * @param prefix The prefix whose stack of URIs is to be pushed + * @param uri The URI to be pushed onto the stack + */ + public void pushPrefixMapping(String prefix, String uri) { + LinkedList stack = (LinkedList) xmlPrefixMapper.get(prefix); + if (stack == null) { + stack = new LinkedList(); + xmlPrefixMapper.put(prefix, stack); + } + stack.addFirst(uri); + } + + /* + * Removes the URI at the top of the stack of URIs to which the given + * prefix is mapped. + * + * @param prefix The prefix whose stack of URIs is to be popped + */ + public void popPrefixMapping(String prefix) { + LinkedList stack = (LinkedList) xmlPrefixMapper.get(prefix); + if (stack == null || stack.size() == 0) { + // XXX throw new Exception("XXX"); + } + stack.removeFirst(); + } + + /* + * Returns the URI to which the given prefix maps. + * + * @param prefix The prefix whose URI is sought + * + * @return The URI to which the given prefix maps + */ + public String getURI(String prefix) { + + String uri = null; + + LinkedList stack = (LinkedList) xmlPrefixMapper.get(prefix); + if (stack == null || stack.size() == 0) { + uri = (String) jspPrefixMapper.get(prefix); + } else { + uri = (String) stack.getFirst(); + } + + return uri; + } + + + /* Page/Tag directive attributes */ + + /* + * language + */ + public void setLanguage(String value, Node n, ErrorDispatcher err, + boolean pagedir) + throws JasperException { + + if (!"java".equalsIgnoreCase(value)) { + if (pagedir) + err.jspError(n, "jsp.error.page.language.nonjava"); + else + err.jspError(n, "jsp.error.tag.language.nonjava"); + } + + language = value; + } + + public String getLanguage(boolean useDefault) { + return (language == null && useDefault ? defaultLanguage : language); + } + + public String getLanguage() { + return getLanguage(true); + } + + + /* + * extends + */ + public void setExtends(String value, Node.PageDirective n) { + + xtends = value; + + /* + * If page superclass is top level class (i.e. not in a package) + * explicitly import it. If this is not done, the compiler will assume + * the extended class is in the same pkg as the generated servlet. + */ + if (value.indexOf('.') < 0) + n.addImport(value); + } + + /** + * Gets the value of the 'extends' page directive attribute. + * + * @param useDefault TRUE if the default + * (org.apache.jasper.runtime.HttpJspBase) should be returned if this + * attribute has not been set, FALSE otherwise + * + * @return The value of the 'extends' page directive attribute, or the + * default (org.apache.jasper.runtime.HttpJspBase) if this attribute has + * not been set and useDefault is TRUE + */ + public String getExtends(boolean useDefault) { + return (xtends == null && useDefault ? defaultExtends : xtends); + } + + /** + * Gets the value of the 'extends' page directive attribute. + * + * @return The value of the 'extends' page directive attribute, or the + * default (org.apache.jasper.runtime.HttpJspBase) if this attribute has + * not been set + */ + public String getExtends() { + return getExtends(true); + } + + + /* + * contentType + */ + public void setContentType(String value) { + contentType = value; + } + + public String getContentType() { + return contentType; + } + + + /* + * buffer + */ + public void setBufferValue(String value, Node n, ErrorDispatcher err) + throws JasperException { + + if ("none".equalsIgnoreCase(value)) + buffer = 0; + else { + if (value == null || !value.endsWith("kb")) + err.jspError(n, "jsp.error.page.invalid.buffer"); + try { + Integer k = new Integer(value.substring(0, value.length()-2)); + buffer = k.intValue() * 1024; + } catch (NumberFormatException e) { + err.jspError(n, "jsp.error.page.invalid.buffer"); + } + } + + bufferValue = value; + } + + public String getBufferValue() { + return bufferValue; + } + + public int getBuffer() { + return buffer; + } + + + /* + * session + */ + public void setSession(String value, Node n, ErrorDispatcher err) + throws JasperException { + + if ("true".equalsIgnoreCase(value)) + isSession = true; + else if ("false".equalsIgnoreCase(value)) + isSession = false; + else + err.jspError(n, "jsp.error.page.invalid.session"); + + session = value; + } + + public String getSession() { + return session; + } + + public boolean isSession() { + return isSession; + } + + + /* + * autoFlush + */ + public void setAutoFlush(String value, Node n, ErrorDispatcher err) + throws JasperException { + + if ("true".equalsIgnoreCase(value)) + isAutoFlush = true; + else if ("false".equalsIgnoreCase(value)) + isAutoFlush = false; + else + err.jspError(n, "jsp.error.autoFlush.invalid"); + + autoFlush = value; + } + + public String getAutoFlush() { + return autoFlush; + } + + public boolean isAutoFlush() { + return isAutoFlush; + } + + + /* + * isThreadSafe + */ + public void setIsThreadSafe(String value, Node n, ErrorDispatcher err) + throws JasperException { + + if ("true".equalsIgnoreCase(value)) + isThreadSafe = true; + else if ("false".equalsIgnoreCase(value)) + isThreadSafe = false; + else + err.jspError(n, "jsp.error.page.invalid.isthreadsafe"); + + isThreadSafeValue = value; + } + + public String getIsThreadSafe() { + return isThreadSafeValue; + } + + public boolean isThreadSafe() { + return isThreadSafe; + } + + + /* + * info + */ + public void setInfo(String value) { + info = value; + } + + public String getInfo() { + return info; + } + + + /* + * errorPage + */ + public void setErrorPage(String value) { + errorPage = value; + } + + public String getErrorPage() { + return errorPage; + } + + + /* + * isErrorPage + */ + public void setIsErrorPage(String value, Node n, ErrorDispatcher err) + throws JasperException { + + if ("true".equalsIgnoreCase(value)) + isErrorPage = true; + else if ("false".equalsIgnoreCase(value)) + isErrorPage = false; + else + err.jspError(n, "jsp.error.page.invalid.iserrorpage"); + + isErrorPageValue = value; + } + + public String getIsErrorPage() { + return isErrorPageValue; + } + + public boolean isErrorPage() { + return isErrorPage; + } + + + /* + * isELIgnored + */ + public void setIsELIgnored(String value, Node n, ErrorDispatcher err, + boolean pagedir) + throws JasperException { + + if ("true".equalsIgnoreCase(value)) + isELIgnored = true; + else if ("false".equalsIgnoreCase(value)) + isELIgnored = false; + else { + if (pagedir) + err.jspError(n, "jsp.error.page.invalid.iselignored"); + else + err.jspError(n, "jsp.error.tag.invalid.iselignored"); + } + + isELIgnoredValue = value; + } + + /* + * deferredSyntaxAllowedAsLiteral + */ + public void setDeferredSyntaxAllowedAsLiteral(String value, Node n, ErrorDispatcher err, + boolean pagedir) + throws JasperException { + + if ("true".equalsIgnoreCase(value)) + deferredSyntaxAllowedAsLiteral = true; + else if ("false".equalsIgnoreCase(value)) + deferredSyntaxAllowedAsLiteral = false; + else { + if (pagedir) + err.jspError(n, "jsp.error.page.invalid.deferredsyntaxallowedasliteral"); + else + err.jspError(n, "jsp.error.tag.invalid.deferredsyntaxallowedasliteral"); + } + + deferredSyntaxAllowedAsLiteralValue = value; + } + + /* + * trimDirectiveWhitespaces + */ + public void setTrimDirectiveWhitespaces(String value, Node n, ErrorDispatcher err, + boolean pagedir) + throws JasperException { + + if ("true".equalsIgnoreCase(value)) + trimDirectiveWhitespaces = true; + else if ("false".equalsIgnoreCase(value)) + trimDirectiveWhitespaces = false; + else { + if (pagedir) + err.jspError(n, "jsp.error.page.invalid.trimdirectivewhitespaces"); + else + err.jspError(n, "jsp.error.tag.invalid.trimdirectivewhitespaces"); + } + + trimDirectiveWhitespacesValue = value; + } + + public void setELIgnored(boolean s) { + isELIgnored = s; + } + + public String getIsELIgnored() { + return isELIgnoredValue; + } + + public boolean isELIgnored() { + return isELIgnored; + } + + public void putNonCustomTagPrefix(String prefix, Mark where) { + nonCustomTagPrefixMap.put(prefix, where); + } + + public Mark getNonCustomTagPrefix(String prefix) { + return (Mark) nonCustomTagPrefixMap.get(prefix); + } + + public String getDeferredSyntaxAllowedAsLiteral() { + return deferredSyntaxAllowedAsLiteralValue; + } + + public boolean isDeferredSyntaxAllowedAsLiteral() { + return deferredSyntaxAllowedAsLiteral; + } + + public void setDeferredSyntaxAllowedAsLiteral(boolean isELDeferred) { + this.deferredSyntaxAllowedAsLiteral = isELDeferred; + } + + public ExpressionFactory getExpressionFactory() { + return expressionFactory; + } + + public String getTrimDirectiveWhitespaces() { + return trimDirectiveWhitespacesValue; + } + + public boolean isTrimDirectiveWhitespaces() { + return trimDirectiveWhitespaces; + } + + public void setTrimDirectiveWhitespaces(boolean trimDirectiveWhitespaces) { + this.trimDirectiveWhitespaces = trimDirectiveWhitespaces; + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Parser.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Parser.java new file mode 100644 index 000000000..f302e99df --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Parser.java @@ -0,0 +1,1791 @@ +/* + * 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.CharArrayWriter; +import java.io.FileNotFoundException; +import java.net.URL; +import java.util.Iterator; +import java.util.List; + +import javax.servlet.jsp.tagext.TagAttributeInfo; +import javax.servlet.jsp.tagext.TagFileInfo; +import javax.servlet.jsp.tagext.TagInfo; +import javax.servlet.jsp.tagext.TagLibraryInfo; + +import org.apache.jasper.JasperException; +import org.apache.jasper.JspCompilationContext; +import org.xml.sax.Attributes; +import org.xml.sax.helpers.AttributesImpl; + +/** + * This class implements a parser for a JSP page (non-xml view). JSP page + * grammar is included here for reference. The token '#' that appears in the + * production indicates the current input token location in the production. + * + * @author Kin-man Chung + * @author Shawn Bayern + * @author Mark Roth + */ + +class Parser implements TagConstants { + + private ParserController parserController; + + private JspCompilationContext ctxt; + + private JspReader reader; + + private String currentFile; + + private Mark start; + + private ErrorDispatcher err; + + private int scriptlessCount; + + private boolean isTagFile; + + private boolean directivesOnly; + + private URL jarFileUrl; + + private PageInfo pageInfo; + + // Virtual body content types, to make parsing a little easier. + // These are not accessible from outside the parser. + private static final String JAVAX_BODY_CONTENT_PARAM = "JAVAX_BODY_CONTENT_PARAM"; + + private static final String JAVAX_BODY_CONTENT_PLUGIN = "JAVAX_BODY_CONTENT_PLUGIN"; + + private static final String JAVAX_BODY_CONTENT_TEMPLATE_TEXT = "JAVAX_BODY_CONTENT_TEMPLATE_TEXT"; + + private static final boolean STRICT_QUOTE_ESCAPING = Boolean.valueOf( + System.getProperty( + "org.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING", + "true")).booleanValue(); + + /** + * The constructor + */ + private Parser(ParserController pc, JspReader reader, boolean isTagFile, + boolean directivesOnly, URL jarFileUrl) { + this.parserController = pc; + this.ctxt = pc.getJspCompilationContext(); + this.pageInfo = pc.getCompiler().getPageInfo(); + this.err = pc.getCompiler().getErrorDispatcher(); + this.reader = reader; + this.currentFile = reader.mark().getFile(); + this.scriptlessCount = 0; + this.isTagFile = isTagFile; + this.directivesOnly = directivesOnly; + this.jarFileUrl = jarFileUrl; + start = reader.mark(); + } + + /** + * The main entry for Parser + * + * @param pc + * The ParseController, use for getting other objects in compiler + * and for parsing included pages + * @param reader + * To read the page + * @param parent + * The parent node to this page, null for top level page + * @return list of nodes representing the parsed page + */ + public static Node.Nodes parse(ParserController pc, JspReader reader, + Node parent, boolean isTagFile, boolean directivesOnly, + URL jarFileUrl, String pageEnc, String jspConfigPageEnc, + boolean isDefaultPageEncoding, boolean isBomPresent) throws JasperException { + + Parser parser = new Parser(pc, reader, isTagFile, directivesOnly, + jarFileUrl); + + Node.Root root = new Node.Root(reader.mark(), parent, false); + root.setPageEncoding(pageEnc); + root.setJspConfigPageEncoding(jspConfigPageEnc); + root.setIsDefaultPageEncoding(isDefaultPageEncoding); + root.setIsBomPresent(isBomPresent); + + if (directivesOnly) { + parser.parseTagFileDirectives(root); + return new Node.Nodes(root); + } + + // For the Top level page, add inlcude-prelude and include-coda + PageInfo pageInfo = pc.getCompiler().getPageInfo(); + if (parent == null) { + parser.addInclude(root, pageInfo.getIncludePrelude()); + } + while (reader.hasMoreInput()) { + parser.parseElements(root); + } + if (parent == null) { + parser.addInclude(root, pageInfo.getIncludeCoda()); + } + + Node.Nodes page = new Node.Nodes(root); + return page; + } + + /** + * Attributes ::= (S Attribute)* S? + */ + Attributes parseAttributes() throws JasperException { + AttributesImpl attrs = new AttributesImpl(); + + reader.skipSpaces(); + while (parseAttribute(attrs)) + reader.skipSpaces(); + + return attrs; + } + + /** + * Parse Attributes for a reader, provided for external use + */ + public static Attributes parseAttributes(ParserController pc, + JspReader reader) throws JasperException { + Parser tmpParser = new Parser(pc, reader, false, false, null); + return tmpParser.parseAttributes(); + } + + /** + * Attribute ::= Name S? Eq S? ( '"<%=' RTAttributeValueDouble | '"' + * AttributeValueDouble | "'<%=" RTAttributeValueSingle | "'" + * AttributeValueSingle } Note: JSP and XML spec does not allow while spaces + * around Eq. It is added to be backward compatible with Tomcat, and with + * other xml parsers. + */ + private boolean parseAttribute(AttributesImpl attrs) throws JasperException { + + // Get the qualified name + String qName = parseName(); + if (qName == null) + return false; + + // Determine prefix and local name components + String localName = qName; + String uri = ""; + int index = qName.indexOf(':'); + if (index != -1) { + String prefix = qName.substring(0, index); + uri = pageInfo.getURI(prefix); + if (uri == null) { + err.jspError(reader.mark(), + "jsp.error.attribute.invalidPrefix", prefix); + } + localName = qName.substring(index + 1); + } + + reader.skipSpaces(); + if (!reader.matches("=")) + err.jspError(reader.mark(), "jsp.error.attribute.noequal"); + + reader.skipSpaces(); + char quote = (char) reader.nextChar(); + if (quote != '\'' && quote != '"') + err.jspError(reader.mark(), "jsp.error.attribute.noquote"); + + String watchString = ""; + if (reader.matches("<%=")) + watchString = "%>"; + watchString = watchString + quote; + + String attrValue = parseAttributeValue(watchString); + attrs.addAttribute(uri, localName, qName, "CDATA", attrValue); + return true; + } + + /** + * Name ::= (Letter | '_' | ':') (Letter | Digit | '.' | '_' | '-' | ':')* + */ + private String parseName() throws JasperException { + char ch = (char) reader.peekChar(); + if (Character.isLetter(ch) || ch == '_' || ch == ':') { + StringBuffer buf = new StringBuffer(); + buf.append(ch); + reader.nextChar(); + ch = (char) reader.peekChar(); + while (Character.isLetter(ch) || Character.isDigit(ch) || ch == '.' + || ch == '_' || ch == '-' || ch == ':') { + buf.append(ch); + reader.nextChar(); + ch = (char) reader.peekChar(); + } + return buf.toString(); + } + return null; + } + + /** + * AttributeValueDouble ::= (QuotedChar - '"')* ('"' | ) + * RTAttributeValueDouble ::= ((QuotedChar - '"')* - ((QuotedChar-'"')'%>"') + * ('%>"' | TRANSLATION_ERROR) + */ + private String parseAttributeValue(String watch) throws JasperException { + Mark start = reader.mark(); + Mark stop = reader.skipUntilIgnoreEsc(watch); + if (stop == null) { + err.jspError(start, "jsp.error.attribute.unterminated", watch); + } + + String ret = parseQuoted(start, reader.getText(start, stop), + watch.charAt(watch.length() - 1)); + if (watch.length() == 1) // quote + return ret; + + // putback delimiter '<%=' and '%>', since they are needed if the + // attribute does not allow RTexpression. + return "<%=" + ret + "%>"; + } + + /** + * QuotedChar ::= ''' | '"' | '\\' | '\"' | "\'" | '\>' | '\$' | + * Char + */ + private String parseQuoted(Mark start, String tx, char quote) + throws JasperException { + StringBuffer buf = new StringBuffer(); + int size = tx.length(); + int i = 0; + while (i < size) { + char ch = tx.charAt(i); + if (ch == '&') { + if (i + 5 < size && tx.charAt(i + 1) == 'a' + && tx.charAt(i + 2) == 'p' && tx.charAt(i + 3) == 'o' + && tx.charAt(i + 4) == 's' && tx.charAt(i + 5) == ';') { + buf.append('\''); + i += 6; + } else if (i + 5 < size && tx.charAt(i + 1) == 'q' + && tx.charAt(i + 2) == 'u' && tx.charAt(i + 3) == 'o' + && tx.charAt(i + 4) == 't' && tx.charAt(i + 5) == ';') { + buf.append('"'); + i += 6; + } else { + buf.append(ch); + ++i; + } + } else if (ch == '\\' && i + 1 < size) { + ch = tx.charAt(i + 1); + if (ch == '\\' || ch == '\"' || ch == '\'' || ch == '>') { + // \ " and ' are always unescaped regardless of if they are + // inside or outside of an EL expression. JSP.1.6 takes + // precedence over JSP.1.3.10 (confirmed with EG). + buf.append(ch); + i += 2; + } else { + buf.append('\\'); + ++i; + } + } else if (ch == quote && STRICT_QUOTE_ESCAPING) { + // Unescaped quote character + err.jspError(start, "jsp.error.attribute.noescape", tx, + "" + quote); + } else { + buf.append(ch); + ++i; + } + } + return buf.toString(); + } + + private String parseScriptText(String tx) { + CharArrayWriter cw = new CharArrayWriter(); + int size = tx.length(); + int i = 0; + while (i < size) { + char ch = tx.charAt(i); + if (i + 2 < size && ch == '%' && tx.charAt(i + 1) == '\\' + && tx.charAt(i + 2) == '>') { + cw.write('%'); + cw.write('>'); + i += 3; + } else { + cw.write(ch); + ++i; + } + } + cw.close(); + return cw.toString(); + } + + /* + * Invokes parserController to parse the included page + */ + private void processIncludeDirective(String file, Node parent) + throws JasperException { + if (file == null) { + return; + } + + try { + parserController.parse(file, parent, jarFileUrl); + } catch (FileNotFoundException ex) { + err.jspError(start, "jsp.error.file.not.found", file); + } catch (Exception ex) { + err.jspError(start, ex.getMessage()); + } + } + + /* + * Parses a page directive with the following syntax: PageDirective ::= ( S + * Attribute)* + */ + private void parsePageDirective(Node parent) throws JasperException { + Attributes attrs = parseAttributes(); + Node.PageDirective n = new Node.PageDirective(attrs, start, parent); + + /* + * A page directive may contain multiple 'import' attributes, each of + * which consists of a comma-separated list of package names. Store each + * list with the node, where it is parsed. + */ + for (int i = 0; i < attrs.getLength(); i++) { + if ("import".equals(attrs.getQName(i))) { + n.addImport(attrs.getValue(i)); + } + } + } + + /* + * Parses an include directive with the following syntax: IncludeDirective + * ::= ( S Attribute)* + */ + private void parseIncludeDirective(Node parent) throws JasperException { + Attributes attrs = parseAttributes(); + + // Included file expanded here + Node includeNode = new Node.IncludeDirective(attrs, start, parent); + processIncludeDirective(attrs.getValue("file"), includeNode); + } + + /** + * Add a list of files. This is used for implementing include-prelude and + * include-coda of jsp-config element in web.xml + */ + private void addInclude(Node parent, List files) throws JasperException { + if (files != null) { + Iterator iter = files.iterator(); + while (iter.hasNext()) { + String file = (String) iter.next(); + AttributesImpl attrs = new AttributesImpl(); + attrs.addAttribute("", "file", "file", "CDATA", file); + + // Create a dummy Include directive node + Node includeNode = new Node.IncludeDirective(attrs, reader + .mark(), parent); + processIncludeDirective(file, includeNode); + } + } + } + + /* + * Parses a taglib directive with the following syntax: Directive ::= ( S + * Attribute)* + */ + private void parseTaglibDirective(Node parent) throws JasperException { + + Attributes attrs = parseAttributes(); + String uri = attrs.getValue("uri"); + String prefix = attrs.getValue("prefix"); + if (prefix != null) { + Mark prevMark = pageInfo.getNonCustomTagPrefix(prefix); + if (prevMark != null) { + err.jspError(reader.mark(), "jsp.error.prefix.use_before_dcl", + prefix, prevMark.getFile(), "" + + prevMark.getLineNumber()); + } + if (uri != null) { + String uriPrev = pageInfo.getURI(prefix); + if (uriPrev != null && !uriPrev.equals(uri)) { + err.jspError(reader.mark(), "jsp.error.prefix.refined", + prefix, uri, uriPrev); + } + if (pageInfo.getTaglib(uri) == null) { + TagLibraryInfoImpl impl = null; + if (ctxt.getOptions().isCaching()) { + impl = (TagLibraryInfoImpl) ctxt.getOptions() + .getCache().get(uri); + } + if (impl == null) { + String[] location = ctxt.getTldLocation(uri); + impl = new TagLibraryInfoImpl(ctxt, parserController, pageInfo, + prefix, uri, location, err); + if (ctxt.getOptions().isCaching()) { + ctxt.getOptions().getCache().put(uri, impl); + } + } else { + // Current compilation context needs location of cached + // tag files + for (TagFileInfo info : impl.getTagFiles()) { + ctxt.setTagFileJarUrl(info.getPath(), + ctxt.getTagFileJarUrl()); + } + } + pageInfo.addTaglib(uri, impl); + } + pageInfo.addPrefixMapping(prefix, uri); + } else { + String tagdir = attrs.getValue("tagdir"); + if (tagdir != null) { + String urnTagdir = URN_JSPTAGDIR + tagdir; + if (pageInfo.getTaglib(urnTagdir) == null) { + pageInfo.addTaglib(urnTagdir, + new ImplicitTagLibraryInfo(ctxt, + parserController, pageInfo, prefix, tagdir, err)); + } + pageInfo.addPrefixMapping(prefix, urnTagdir); + } + } + } + + new Node.TaglibDirective(attrs, start, parent); + } + + /* + * Parses a directive with the following syntax: Directive ::= S? ( 'page' + * PageDirective | 'include' IncludeDirective | 'taglib' TagLibDirective) S? + * '%>' + * + * TagDirective ::= S? ('tag' PageDirective | 'include' IncludeDirective | + * 'taglib' TagLibDirective) | 'attribute AttributeDirective | 'variable + * VariableDirective S? '%>' + */ + private void parseDirective(Node parent) throws JasperException { + reader.skipSpaces(); + + String directive = null; + if (reader.matches("page")) { + directive = "<%@ page"; + if (isTagFile) { + err.jspError(reader.mark(), "jsp.error.directive.istagfile", + directive); + } + parsePageDirective(parent); + } else if (reader.matches("include")) { + directive = "<%@ include"; + parseIncludeDirective(parent); + } else if (reader.matches("taglib")) { + if (directivesOnly) { + // No need to get the tagLibInfo objects. This alos suppresses + // parsing of any tag files used in this tag file. + return; + } + directive = "<%@ taglib"; + parseTaglibDirective(parent); + } else if (reader.matches("tag")) { + directive = "<%@ tag"; + if (!isTagFile) { + err.jspError(reader.mark(), "jsp.error.directive.isnottagfile", + directive); + } + parseTagDirective(parent); + } else if (reader.matches("attribute")) { + directive = "<%@ attribute"; + if (!isTagFile) { + err.jspError(reader.mark(), "jsp.error.directive.isnottagfile", + directive); + } + parseAttributeDirective(parent); + } else if (reader.matches("variable")) { + directive = "<%@ variable"; + if (!isTagFile) { + err.jspError(reader.mark(), "jsp.error.directive.isnottagfile", + directive); + } + parseVariableDirective(parent); + } else { + err.jspError(reader.mark(), "jsp.error.invalid.directive"); + } + + reader.skipSpaces(); + if (!reader.matches("%>")) { + err.jspError(start, "jsp.error.unterminated", directive); + } + } + + /* + * Parses a directive with the following syntax: + * + * XMLJSPDirectiveBody ::= S? ( ( 'page' PageDirectiveAttrList S? ( '/>' | ( + * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>' + * S? ETag ) ) | + * + * XMLTagDefDirectiveBody ::= ( ( 'tag' TagDirectiveAttrList S? ( '/>' | ( + * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>' + * S? ETag ) ) | ( 'attribute' AttributeDirectiveAttrList S? ( '/>' | ( '>' + * S? ETag ) ) | ( 'variable' VariableDirectiveAttrList S? ( '/>' | ( '>' S? + * ETag ) ) ) | + */ + private void parseXMLDirective(Node parent) throws JasperException { + reader.skipSpaces(); + + String eTag = null; + if (reader.matches("page")) { + eTag = "jsp:directive.page"; + if (isTagFile) { + err.jspError(reader.mark(), "jsp.error.directive.istagfile", + "<" + eTag); + } + parsePageDirective(parent); + } else if (reader.matches("include")) { + eTag = "jsp:directive.include"; + parseIncludeDirective(parent); + } else if (reader.matches("tag")) { + eTag = "jsp:directive.tag"; + if (!isTagFile) { + err.jspError(reader.mark(), "jsp.error.directive.isnottagfile", + "<" + eTag); + } + parseTagDirective(parent); + } else if (reader.matches("attribute")) { + eTag = "jsp:directive.attribute"; + if (!isTagFile) { + err.jspError(reader.mark(), "jsp.error.directive.isnottagfile", + "<" + eTag); + } + parseAttributeDirective(parent); + } else if (reader.matches("variable")) { + eTag = "jsp:directive.variable"; + if (!isTagFile) { + err.jspError(reader.mark(), "jsp.error.directive.isnottagfile", + "<" + eTag); + } + parseVariableDirective(parent); + } else { + err.jspError(reader.mark(), "jsp.error.invalid.directive"); + } + + reader.skipSpaces(); + if (reader.matches(">")) { + reader.skipSpaces(); + if (!reader.matchesETag(eTag)) { + err.jspError(start, "jsp.error.unterminated", "<" + eTag); + } + } else if (!reader.matches("/>")) { + err.jspError(start, "jsp.error.unterminated", "<" + eTag); + } + } + + /* + * Parses a tag directive with the following syntax: PageDirective ::= ( S + * Attribute)* + */ + private void parseTagDirective(Node parent) throws JasperException { + Attributes attrs = parseAttributes(); + Node.TagDirective n = new Node.TagDirective(attrs, start, parent); + + /* + * A page directive may contain multiple 'import' attributes, each of + * which consists of a comma-separated list of package names. Store each + * list with the node, where it is parsed. + */ + for (int i = 0; i < attrs.getLength(); i++) { + if ("import".equals(attrs.getQName(i))) { + n.addImport(attrs.getValue(i)); + } + } + } + + /* + * Parses a attribute directive with the following syntax: + * AttributeDirective ::= ( S Attribute)* + */ + private void parseAttributeDirective(Node parent) throws JasperException { + Attributes attrs = parseAttributes(); + Node.AttributeDirective n = new Node.AttributeDirective(attrs, start, + parent); + } + + /* + * Parses a variable directive with the following syntax: PageDirective ::= ( + * S Attribute)* + */ + private void parseVariableDirective(Node parent) throws JasperException { + Attributes attrs = parseAttributes(); + Node.VariableDirective n = new Node.VariableDirective(attrs, start, + parent); + } + + /* + * JSPCommentBody ::= (Char* - (Char* '--%>')) '--%>' + */ + private void parseComment(Node parent) throws JasperException { + start = reader.mark(); + Mark stop = reader.skipUntil("--%>"); + if (stop == null) { + err.jspError(start, "jsp.error.unterminated", "<%--"); + } + + new Node.Comment(reader.getText(start, stop), start, parent); + } + + /* + * DeclarationBody ::= (Char* - (char* '%>')) '%>' + */ + private void parseDeclaration(Node parent) throws JasperException { + start = reader.mark(); + Mark stop = reader.skipUntil("%>"); + if (stop == null) { + err.jspError(start, "jsp.error.unterminated", "<%!"); + } + + new Node.Declaration(parseScriptText(reader.getText(start, stop)), + start, parent); + } + + /* + * XMLDeclarationBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<')) + * CDSect?)* ETag | CDSect ::= CDStart CData CDEnd + * CDStart ::= '' Char*)) CDEnd + * ::= ']]>' + */ + private void parseXMLDeclaration(Node parent) throws JasperException { + reader.skipSpaces(); + if (!reader.matches("/>")) { + if (!reader.matches(">")) { + err.jspError(start, "jsp.error.unterminated", + "<jsp:declaration>"); + } + Mark stop; + String text; + while (true) { + start = reader.mark(); + stop = reader.skipUntil("<"); + if (stop == null) { + err.jspError(start, "jsp.error.unterminated", + "<jsp:declaration>"); + } + text = parseScriptText(reader.getText(start, stop)); + new Node.Declaration(text, start, parent); + if (reader.matches("![CDATA[")) { + start = reader.mark(); + stop = reader.skipUntil("]]>"); + if (stop == null) { + err.jspError(start, "jsp.error.unterminated", "CDATA"); + } + text = parseScriptText(reader.getText(start, stop)); + new Node.Declaration(text, start, parent); + } else { + break; + } + } + + if (!reader.matchesETagWithoutLessThan("jsp:declaration")) { + err.jspError(start, "jsp.error.unterminated", + "<jsp:declaration>"); + } + } + } + + /* + * ExpressionBody ::= (Char* - (char* '%>')) '%>' + */ + private void parseExpression(Node parent) throws JasperException { + start = reader.mark(); + Mark stop = reader.skipUntil("%>"); + if (stop == null) { + err.jspError(start, "jsp.error.unterminated", "<%="); + } + + new Node.Expression(parseScriptText(reader.getText(start, stop)), + start, parent); + } + + /* + * XMLExpressionBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<')) + * CDSect?)* ETag ) | + */ + private void parseXMLExpression(Node parent) throws JasperException { + reader.skipSpaces(); + if (!reader.matches("/>")) { + if (!reader.matches(">")) { + err.jspError(start, "jsp.error.unterminated", + "<jsp:expression>"); + } + Mark stop; + String text; + while (true) { + start = reader.mark(); + stop = reader.skipUntil("<"); + if (stop == null) { + err.jspError(start, "jsp.error.unterminated", + "<jsp:expression>"); + } + text = parseScriptText(reader.getText(start, stop)); + new Node.Expression(text, start, parent); + if (reader.matches("![CDATA[")) { + start = reader.mark(); + stop = reader.skipUntil("]]>"); + if (stop == null) { + err.jspError(start, "jsp.error.unterminated", "CDATA"); + } + text = parseScriptText(reader.getText(start, stop)); + new Node.Expression(text, start, parent); + } else { + break; + } + } + if (!reader.matchesETagWithoutLessThan("jsp:expression")) { + err.jspError(start, "jsp.error.unterminated", + "<jsp:expression>"); + } + } + } + + /* + * ELExpressionBody (following "${" to first unquoted "}") // XXX add formal + * production and confirm implementation against it, // once it's decided + */ + private void parseELExpression(Node parent, char type) throws JasperException { + start = reader.mark(); + Mark last = null; + boolean singleQuoted = false, doubleQuoted = false; + int currentChar; + do { + // XXX could move this logic to JspReader + last = reader.mark(); // XXX somewhat wasteful + currentChar = reader.nextChar(); + if (currentChar == '\\' && (singleQuoted || doubleQuoted)) { + // skip character following '\' within quotes + reader.nextChar(); + currentChar = reader.nextChar(); + } + if (currentChar == -1) + err.jspError(start, "jsp.error.unterminated", type + "{"); + if (currentChar == '"' && !singleQuoted) + doubleQuoted = !doubleQuoted; + if (currentChar == '\'' && !doubleQuoted) + singleQuoted = !singleQuoted; + } while (currentChar != '}' || (singleQuoted || doubleQuoted)); + + new Node.ELExpression(type, reader.getText(start, last), start, parent); + } + + /* + * ScriptletBody ::= (Char* - (char* '%>')) '%>' + */ + private void parseScriptlet(Node parent) throws JasperException { + start = reader.mark(); + Mark stop = reader.skipUntil("%>"); + if (stop == null) { + err.jspError(start, "jsp.error.unterminated", "<%"); + } + + new Node.Scriptlet(parseScriptText(reader.getText(start, stop)), start, + parent); + } + + /* + * XMLScriptletBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<')) + * CDSect?)* ETag ) | + */ + private void parseXMLScriptlet(Node parent) throws JasperException { + reader.skipSpaces(); + if (!reader.matches("/>")) { + if (!reader.matches(">")) { + err.jspError(start, "jsp.error.unterminated", + "<jsp:scriptlet>"); + } + Mark stop; + String text; + while (true) { + start = reader.mark(); + stop = reader.skipUntil("<"); + if (stop == null) { + err.jspError(start, "jsp.error.unterminated", + "<jsp:scriptlet>"); + } + text = parseScriptText(reader.getText(start, stop)); + new Node.Scriptlet(text, start, parent); + if (reader.matches("![CDATA[")) { + start = reader.mark(); + stop = reader.skipUntil("]]>"); + if (stop == null) { + err.jspError(start, "jsp.error.unterminated", "CDATA"); + } + text = parseScriptText(reader.getText(start, stop)); + new Node.Scriptlet(text, start, parent); + } else { + break; + } + } + + if (!reader.matchesETagWithoutLessThan("jsp:scriptlet")) { + err.jspError(start, "jsp.error.unterminated", + "<jsp:scriptlet>"); + } + } + } + + /** + * Param ::= '' S? ( ' ) S? ETag ) | ( '>' S? Param* ETag ) + * + * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '' Param* '' + */ + private void parseInclude(Node parent) throws JasperException { + Attributes attrs = parseAttributes(); + reader.skipSpaces(); + + Node includeNode = new Node.IncludeAction(attrs, start, parent); + + parseOptionalBody(includeNode, "jsp:include", JAVAX_BODY_CONTENT_PARAM); + } + + /* + * For Forward: StdActionContent ::= Attributes ParamBody + */ + private void parseForward(Node parent) throws JasperException { + Attributes attrs = parseAttributes(); + reader.skipSpaces(); + + Node forwardNode = new Node.ForwardAction(attrs, start, parent); + + parseOptionalBody(forwardNode, "jsp:forward", JAVAX_BODY_CONTENT_PARAM); + } + + private void parseInvoke(Node parent) throws JasperException { + Attributes attrs = parseAttributes(); + reader.skipSpaces(); + + Node invokeNode = new Node.InvokeAction(attrs, start, parent); + + parseEmptyBody(invokeNode, "jsp:invoke"); + } + + private void parseDoBody(Node parent) throws JasperException { + Attributes attrs = parseAttributes(); + reader.skipSpaces(); + + Node doBodyNode = new Node.DoBodyAction(attrs, start, parent); + + parseEmptyBody(doBodyNode, "jsp:doBody"); + } + + private void parseElement(Node parent) throws JasperException { + Attributes attrs = parseAttributes(); + reader.skipSpaces(); + + Node elementNode = new Node.JspElement(attrs, start, parent); + + parseOptionalBody(elementNode, "jsp:element", TagInfo.BODY_CONTENT_JSP); + } + + /* + * For GetProperty: StdActionContent ::= Attributes EmptyBody + */ + private void parseGetProperty(Node parent) throws JasperException { + Attributes attrs = parseAttributes(); + reader.skipSpaces(); + + Node getPropertyNode = new Node.GetProperty(attrs, start, parent); + + parseOptionalBody(getPropertyNode, "jsp:getProperty", + TagInfo.BODY_CONTENT_EMPTY); + } + + /* + * For SetProperty: StdActionContent ::= Attributes EmptyBody + */ + private void parseSetProperty(Node parent) throws JasperException { + Attributes attrs = parseAttributes(); + reader.skipSpaces(); + + Node setPropertyNode = new Node.SetProperty(attrs, start, parent); + + parseOptionalBody(setPropertyNode, "jsp:setProperty", + TagInfo.BODY_CONTENT_EMPTY); + } + + /* + * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '")) { + // Done + } else if (reader.matches(">")) { + if (reader.matchesETag(tag)) { + // Done + } else if (reader.matchesOptionalSpacesFollowedBy("' ETag ) | ( '>' S? '' Body ETag ) + * + * ScriptlessActionBody ::= JspAttributeAndBody | ( '>' ScriptlessBody ETag ) + * + * TagDependentActionBody ::= JspAttributeAndBody | ( '>' TagDependentBody + * ETag ) + * + */ + private void parseOptionalBody(Node parent, String tag, String bodyType) + throws JasperException { + if (reader.matches("/>")) { + // EmptyBody + return; + } + + if (!reader.matches(">")) { + err.jspError(reader.mark(), "jsp.error.unterminated", "<" + tag); + } + + if (reader.matchesETag(tag)) { + // EmptyBody + return; + } + + if (!parseJspAttributeAndBody(parent, tag, bodyType)) { + // Must be ( '>' # Body ETag ) + parseBody(parent, tag, bodyType); + } + } + + /** + * Attempts to parse 'JspAttributeAndBody' production. Returns true if it + * matched, or false if not. Assumes EmptyBody is okay as well. + * + * JspAttributeAndBody ::= ( '>' # S? ( ' ) S? ETag ) + */ + private boolean parseJspAttributeAndBody(Node parent, String tag, + String bodyType) throws JasperException { + boolean result = false; + + if (reader.matchesOptionalSpacesFollowedBy(" elements: + parseNamedAttributes(parent); + + result = true; + } + + if (reader.matchesOptionalSpacesFollowedBy(" but something other than + // or the end tag, translation error. + err.jspError(reader.mark(), "jsp.error.jspbody.required", "<" + + tag); + } + + return result; + } + + /* + * Params ::= `>' S? ( ( `' ( ( S? Param+ S? `' ) | + * ) ) | Param+ ) '' + */ + private void parseJspParams(Node parent) throws JasperException { + Node jspParamsNode = new Node.ParamsAction(start, parent); + parseOptionalBody(jspParamsNode, "jsp:params", JAVAX_BODY_CONTENT_PARAM); + } + + /* + * Fallback ::= '/>' | ( `>' S? `' ( ( S? ( Char* - ( Char* `' ) ) `' + * S? ) | ) `' ) | ( '>' ( Char* - ( + * Char* '' ) ) '' ) + */ + private void parseFallBack(Node parent) throws JasperException { + Node fallBackNode = new Node.FallBackAction(start, parent); + parseOptionalBody(fallBackNode, "jsp:fallback", + JAVAX_BODY_CONTENT_TEMPLATE_TEXT); + } + + /* + * For Plugin: StdActionContent ::= Attributes PluginBody + * + * PluginBody ::= EmptyBody | ( '>' S? ( ' ) S? ETag ) | ( '>' S? PluginTags + * ETag ) + * + * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? ' + * + * Attributes ::= ( S Attribute )* S? + * + * CustomActionEnd ::= CustomActionTagDependent | CustomActionJSPContent | + * CustomActionScriptlessContent + * + * CustomActionTagDependent ::= TagDependentOptionalBody + * + * CustomActionJSPContent ::= OptionalBody + * + * CustomActionScriptlessContent ::= ScriptlessOptionalBody + */ + private boolean parseCustomTag(Node parent) throws JasperException { + + if (reader.peekChar() != '<') { + return false; + } + + // Parse 'CustomAction' production (tag prefix and custom action name) + reader.nextChar(); // skip '<' + String tagName = reader.parseToken(false); + int i = tagName.indexOf(':'); + if (i == -1) { + reader.reset(start); + return false; + } + + String prefix = tagName.substring(0, i); + String shortTagName = tagName.substring(i + 1); + + // Check if this is a user-defined tag. + String uri = pageInfo.getURI(prefix); + if (uri == null) { + reader.reset(start); + // Remember the prefix for later error checking + pageInfo.putNonCustomTagPrefix(prefix, reader.mark()); + return false; + } + + TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri); + TagInfo tagInfo = tagLibInfo.getTag(shortTagName); + TagFileInfo tagFileInfo = tagLibInfo.getTagFile(shortTagName); + if (tagInfo == null && tagFileInfo == null) { + err.jspError(start, "jsp.error.bad_tag", shortTagName, prefix); + } + Class tagHandlerClass = null; + if (tagInfo != null) { + // Must be a classic tag, load it here. + // tag files will be loaded later, in TagFileProcessor + String handlerClassName = tagInfo.getTagClassName(); + try { + tagHandlerClass = ctxt.getClassLoader().loadClass( + handlerClassName); + } catch (Exception e) { + err.jspError(start, "jsp.error.loadclass.taghandler", + handlerClassName, tagName); + } + } + + // Parse 'CustomActionBody' production: + // At this point we are committed - if anything fails, we produce + // a translation error. + + // Parse 'Attributes' production: + Attributes attrs = parseAttributes(); + reader.skipSpaces(); + + // Parse 'CustomActionEnd' production: + if (reader.matches("/>")) { + if (tagInfo != null) { + new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs, + start, parent, tagInfo, tagHandlerClass); + } else { + new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs, + start, parent, tagFileInfo); + } + return true; + } + + // Now we parse one of 'CustomActionTagDependent', + // 'CustomActionJSPContent', or 'CustomActionScriptlessContent'. + // depending on body-content in TLD. + + // Looking for a body, it still can be empty; but if there is a + // a tag body, its syntax would be dependent on the type of + // body content declared in the TLD. + String bc; + if (tagInfo != null) { + bc = tagInfo.getBodyContent(); + } else { + bc = tagFileInfo.getTagInfo().getBodyContent(); + } + + Node tagNode = null; + if (tagInfo != null) { + tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri, + attrs, start, parent, tagInfo, tagHandlerClass); + } else { + tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri, + attrs, start, parent, tagFileInfo); + } + + parseOptionalBody(tagNode, tagName, bc); + + return true; + } + + /* + * Parse for a template text string until '<' or "${" or "#{" is encountered, + * recognizing escape sequences "\%", "\$", and "\#". + */ + private void parseTemplateText(Node parent) throws JasperException { + + if (!reader.hasMoreInput()) + return; + + CharArrayWriter ttext = new CharArrayWriter(); + // Output the first character + int ch = reader.nextChar(); + if (ch == '\\') { + reader.pushChar(); + } else { + ttext.write(ch); + } + + while (reader.hasMoreInput()) { + ch = reader.nextChar(); + if (ch == '<') { + reader.pushChar(); + break; + } else if ((ch == '$' || ch == '#') && !pageInfo.isELIgnored()) { + if (!reader.hasMoreInput()) { + ttext.write(ch); + break; + } + if (reader.nextChar() == '{') { + reader.pushChar(); + reader.pushChar(); + break; + } + ttext.write(ch); + reader.pushChar(); + continue; + } else if (ch == '\\') { + if (!reader.hasMoreInput()) { + ttext.write('\\'); + break; + } + char next = (char) reader.peekChar(); + // Looking for \% or \$ or \# + if (next == '%' || ((next == '$' || next == '#') && + !pageInfo.isELIgnored())) { + ch = reader.nextChar(); + } + } + ttext.write(ch); + } + new Node.TemplateText(ttext.toString(), start, parent); + } + + /* + * XMLTemplateText ::= ( S? '/>' ) | ( S? '>' ( ( Char* - ( Char* ( '<' | + * '${' ) ) ) ( '${' ELExpressionBody )? CDSect? )* ETag ) | + * + */ + private void parseXMLTemplateText(Node parent) throws JasperException { + reader.skipSpaces(); + if (!reader.matches("/>")) { + if (!reader.matches(">")) { + err.jspError(start, "jsp.error.unterminated", + "<jsp:text>"); + } + CharArrayWriter ttext = new CharArrayWriter(); + while (reader.hasMoreInput()) { + int ch = reader.nextChar(); + if (ch == '<') { + // Check for "); + if (stop == null) { + err.jspError(start, "jsp.error.unterminated", "CDATA"); + } + String text = reader.getText(start, stop); + ttext.write(text, 0, text.length()); + } else if (ch == '\\') { + if (!reader.hasMoreInput()) { + ttext.write('\\'); + break; + } + ch = reader.nextChar(); + if (ch != '$' && ch != '#') { + ttext.write('\\'); + } + ttext.write(ch); + } else if (ch == '$' || ch == '#') { + if (!reader.hasMoreInput()) { + ttext.write(ch); + break; + } + if (reader.nextChar() != '{') { + ttext.write(ch); + reader.pushChar(); + continue; + } + // Create a template text node + new Node.TemplateText(ttext.toString(), start, parent); + + // Mark and parse the EL expression and create its node: + start = reader.mark(); + parseELExpression(parent, (char) ch); + + start = reader.mark(); + ttext = new CharArrayWriter(); + } else { + ttext.write(ch); + } + } + + new Node.TemplateText(ttext.toString(), start, parent); + + if (!reader.hasMoreInput()) { + err.jspError(start, "jsp.error.unterminated", + "<jsp:text>"); + } else if (!reader.matchesETagWithoutLessThan("jsp:text")) { + err.jspError(start, "jsp.error.jsptext.badcontent"); + } + } + } + + /* + * AllBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( ' 0) { + // vc: ScriptlessBody + // We must follow the ScriptlessBody production if one of + // our parents is ScriptlessBody. + parseElementsScriptless(parent); + return; + } + + start = reader.mark(); + if (reader.matches("<%--")) { + parseComment(parent); + } else if (reader.matches("<%@")) { + parseDirective(parent); + } else if (reader.matches(" ) | ( ' ) | ( '<%=' ) | ( ' ) | ( '<%' ) | ( ' ) | ( ' ) | ( ' ) | ( '<%=' ) | ( ' ) | ( '<%' ) | ( ' ) | ( ' ) | ( '${' + * ) | ( ' ) | TemplateText + */ + private void parseElementsTemplateText(Node parent) throws JasperException { + start = reader.mark(); + if (reader.matches("<%--")) { + parseComment(parent); + } else if (reader.matches("<%@")) { + parseDirective(parent); + } else if (reader.matches("")) { + if (!reader.matches(">")) { + err.jspError(start, "jsp.error.unterminated", "<jsp:body"); + } + parseBody(bodyNode, "jsp:body", bodyType); + } + } + + /* + * Parse the body as JSP content. @param tag The name of the tag whose end + * tag would terminate the body @param bodyType One of the TagInfo body + * types + */ + private void parseBody(Node parent, String tag, String bodyType) + throws JasperException { + if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_TAG_DEPENDENT)) { + parseTagDependentBody(parent, tag); + } else if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_EMPTY)) { + if (!reader.matchesETag(tag)) { + err.jspError(start, "jasper.error.emptybodycontent.nonempty", + tag); + } + } else if (bodyType == JAVAX_BODY_CONTENT_PLUGIN) { + // (note the == since we won't recognize JAVAX_* + // from outside this module). + parsePluginTags(parent); + if (!reader.matchesETag(tag)) { + err.jspError(reader.mark(), "jsp.error.unterminated", "<" + + tag); + } + } else if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP) + || bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS) + || (bodyType == JAVAX_BODY_CONTENT_PARAM) + || (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT)) { + while (reader.hasMoreInput()) { + if (reader.matchesETag(tag)) { + return; + } + + // Check for nested jsp:body or jsp:attribute + if (tag.equals("jsp:body") || tag.equals("jsp:attribute")) { + if (reader.matches("")) { + if (!reader.matches(">")) { + err.jspError(start, "jsp.error.unterminated", + "<jsp:attribute"); + } + if (namedAttributeNode.isTrim()) { + reader.skipSpaces(); + } + parseBody(namedAttributeNode, "jsp:attribute", + getAttributeBodyType(parent, attrs.getValue("name"))); + if (namedAttributeNode.isTrim()) { + Node.Nodes subElems = namedAttributeNode.getBody(); + if (subElems != null) { + Node lastNode = subElems.getNode(subElems.size() - 1); + if (lastNode instanceof Node.TemplateText) { + ((Node.TemplateText) lastNode).rtrim(); + } + } + } + } + reader.skipSpaces(); + } while (reader.matches(" from the enclosing node + */ + private String getAttributeBodyType(Node n, String name) { + + if (n instanceof Node.CustomTag) { + TagInfo tagInfo = ((Node.CustomTag) n).getTagInfo(); + TagAttributeInfo[] tldAttrs = tagInfo.getAttributes(); + for (int i = 0; i < tldAttrs.length; i++) { + if (name.equals(tldAttrs[i].getName())) { + if (tldAttrs[i].isFragment()) { + return TagInfo.BODY_CONTENT_SCRIPTLESS; + } + if (tldAttrs[i].canBeRequestTime()) { + return TagInfo.BODY_CONTENT_JSP; + } + } + } + if (tagInfo.hasDynamicAttributes()) { + return TagInfo.BODY_CONTENT_JSP; + } + } else if (n instanceof Node.IncludeAction) { + if ("page".equals(name)) { + return TagInfo.BODY_CONTENT_JSP; + } + } else if (n instanceof Node.ForwardAction) { + if ("page".equals(name)) { + return TagInfo.BODY_CONTENT_JSP; + } + } else if (n instanceof Node.SetProperty) { + if ("value".equals(name)) { + return TagInfo.BODY_CONTENT_JSP; + } + } else if (n instanceof Node.UseBean) { + if ("beanName".equals(name)) { + return TagInfo.BODY_CONTENT_JSP; + } + } else if (n instanceof Node.PlugIn) { + if ("width".equals(name) || "height".equals(name)) { + return TagInfo.BODY_CONTENT_JSP; + } + } else if (n instanceof Node.ParamAction) { + if ("value".equals(name)) { + return TagInfo.BODY_CONTENT_JSP; + } + } else if (n instanceof Node.JspElement) { + return TagInfo.BODY_CONTENT_JSP; + } + + return JAVAX_BODY_CONTENT_TEMPLATE_TEXT; + } + + private void parseTagFileDirectives(Node parent) throws JasperException { + reader.setSingleFile(true); + reader.skipUntil("<"); + while (reader.hasMoreInput()) { + start = reader.mark(); + if (reader.matches("%--")) { + parseComment(parent); + } else if (reader.matches("%@")) { + parseDirective(parent); + } else if (reader.matches("jsp:directive.")) { + parseXMLDirective(parent); + } + reader.skipUntil("<"); + } + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ParserController.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ParserController.java new file mode 100644 index 000000000..817fd7af4 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ParserController.java @@ -0,0 +1,638 @@ +/* + * 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.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.JarURLConnection; +import java.net.URL; +import java.util.Stack; +import java.util.jar.JarFile; + +import org.apache.jasper.JasperException; +import org.apache.jasper.JspCompilationContext; +import org.apache.jasper.xmlparser.XMLEncodingDetector; +import org.xml.sax.Attributes; + +/** + * Controller for the parsing of a JSP page. + *

+ * The same ParserController instance is used for a JSP page and any JSP + * segments included by it (via an include directive), where each segment may + * be provided in standard or XML syntax. This class selects and invokes the + * appropriate parser for the JSP page and its included segments. + * + * @author Pierre Delisle + * @author Jan Luehe + */ +class ParserController implements TagConstants { + + private static final String CHARSET = "charset="; + + private JspCompilationContext ctxt; + private Compiler compiler; + private ErrorDispatcher err; + + /* + * Indicates the syntax (XML or standard) of the file being processed + */ + private boolean isXml; + + /* + * A stack to keep track of the 'current base directory' + * for include directives that refer to relative paths. + */ + private Stack baseDirStack = new Stack(); + + private boolean isEncodingSpecifiedInProlog; + private boolean isBomPresent; + private int skip; + + private String sourceEnc; + + private boolean isDefaultPageEncoding; + private boolean isTagFile; + private boolean directiveOnly; + + /* + * Constructor + */ + public ParserController(JspCompilationContext ctxt, Compiler compiler) { + this.ctxt = ctxt; + this.compiler = compiler; + this.err = compiler.getErrorDispatcher(); + } + + public JspCompilationContext getJspCompilationContext () { + return ctxt; + } + + public Compiler getCompiler () { + return compiler; + } + + /** + * Parses a JSP page or tag file. This is invoked by the compiler. + * + * @param inFileName The path to the JSP page or tag file to be parsed. + */ + public Node.Nodes parse(String inFileName) + throws FileNotFoundException, JasperException, IOException { + // If we're parsing a packaged tag file or a resource included by it + // (using an include directive), ctxt.getTagFileJar() returns the + // JAR file from which to read the tag file or included resource, + // respectively. + isTagFile = ctxt.isTagFile(); + directiveOnly = false; + return doParse(inFileName, null, ctxt.getTagFileJarUrl()); + } + + /** + * Parses the directives of a JSP page or tag file. This is invoked by the + * compiler. + * + * @param inFileName The path to the JSP page or tag file to be parsed. + */ + public Node.Nodes parseDirectives(String inFileName) + throws FileNotFoundException, JasperException, IOException { + // If we're parsing a packaged tag file or a resource included by it + // (using an include directive), ctxt.getTagFileJar() returns the + // JAR file from which to read the tag file or included resource, + // respectively. + isTagFile = ctxt.isTagFile(); + directiveOnly = true; + return doParse(inFileName, null, ctxt.getTagFileJarUrl()); + } + + + /** + * Processes an include directive with the given path. + * + * @param inFileName The path to the resource to be included. + * @param parent The parent node of the include directive. + * @param jarFile The JAR file from which to read the included resource, + * or null of the included resource is to be read from the filesystem + */ + public Node.Nodes parse(String inFileName, Node parent, + URL jarFileUrl) + throws FileNotFoundException, JasperException, IOException { + // For files that are statically included, isTagfile and directiveOnly + // remain unchanged. + return doParse(inFileName, parent, jarFileUrl); + } + + /** + * Extracts tag file directive information from the tag file with the + * given name. + * + * This is invoked by the compiler + * + * @param inFileName The name of the tag file to be parsed. + * @deprecated Use {@link #parseTagFileDirectives(String, URL)} + * See https://issues.apache.org/bugzilla/show_bug.cgi?id=46471 + */ + public Node.Nodes parseTagFileDirectives(String inFileName) + throws FileNotFoundException, JasperException, IOException { + return parseTagFileDirectives( + inFileName, ctxt.getTagFileJarUrl(inFileName)); + } + + /** + * Extracts tag file directive information from the given tag file. + * + * This is invoked by the compiler + * + * @param inFileName The name of the tag file to be parsed. + * @param tagFileJarUrl The location of the tag file. + */ + public Node.Nodes parseTagFileDirectives(String inFileName, + URL tagFileJarUrl) + throws FileNotFoundException, JasperException, IOException { + boolean isTagFileSave = isTagFile; + boolean directiveOnlySave = directiveOnly; + isTagFile = true; + directiveOnly = true; + Node.Nodes page = doParse(inFileName, null, tagFileJarUrl); + directiveOnly = directiveOnlySave; + isTagFile = isTagFileSave; + return page; + } + + /** + * Parses the JSP page or tag file with the given path name. + * + * @param inFileName The name of the JSP page or tag file to be parsed. + * @param parent The parent node (non-null when processing an include + * directive) + * @param isTagFile true if file to be parsed is tag file, and false if it + * is a regular JSP page + * @param directivesOnly true if the file to be parsed is a tag file and + * we are only interested in the directives needed for constructing a + * TagFileInfo. + * @param jarFile The JAR file from which to read the JSP page or tag file, + * or null if the JSP page or tag file is to be read from the filesystem + */ + private Node.Nodes doParse(String inFileName, + Node parent, + URL jarFileUrl) + throws FileNotFoundException, JasperException, IOException { + + Node.Nodes parsedPage = null; + isEncodingSpecifiedInProlog = false; + isBomPresent = false; + isDefaultPageEncoding = false; + + JarFile jarFile = getJarFile(jarFileUrl); + String absFileName = resolveFileName(inFileName); + String jspConfigPageEnc = getJspConfigPageEncoding(absFileName); + + // Figure out what type of JSP document and encoding type we are + // dealing with + determineSyntaxAndEncoding(absFileName, jarFile, jspConfigPageEnc); + + if (parent != null) { + // Included resource, add to dependent list + if (jarFile == null) { + compiler.getPageInfo().addDependant(absFileName); + } else { + compiler.getPageInfo().addDependant( + jarFileUrl.toExternalForm() + absFileName.substring(1)); + } + } + + if ((isXml && isEncodingSpecifiedInProlog) || isBomPresent) { + /* + * Make sure the encoding explicitly specified in the XML + * prolog (if any) matches that in the JSP config element + * (if any), treating "UTF-16", "UTF-16BE", and "UTF-16LE" as + * identical. + */ + if (jspConfigPageEnc != null && !jspConfigPageEnc.equals(sourceEnc) + && (!jspConfigPageEnc.startsWith("UTF-16") + || !sourceEnc.startsWith("UTF-16"))) { + err.jspError("jsp.error.prolog_config_encoding_mismatch", + sourceEnc, jspConfigPageEnc); + } + } + + // Dispatch to the appropriate parser + if (isXml) { + // JSP document (XML syntax) + // InputStream for jspx page is created and properly closed in + // JspDocumentParser. + parsedPage = JspDocumentParser.parse(this, absFileName, + jarFile, parent, + isTagFile, directiveOnly, + sourceEnc, + jspConfigPageEnc, + isEncodingSpecifiedInProlog, + isBomPresent); + } else { + // Standard syntax + InputStreamReader inStreamReader = null; + try { + inStreamReader = JspUtil.getReader(absFileName, sourceEnc, + jarFile, ctxt, err, skip); + JspReader jspReader = new JspReader(ctxt, absFileName, + sourceEnc, inStreamReader, + err); + parsedPage = Parser.parse(this, jspReader, parent, isTagFile, + directiveOnly, jarFileUrl, + sourceEnc, jspConfigPageEnc, + isDefaultPageEncoding, isBomPresent); + } finally { + if (inStreamReader != null) { + try { + inStreamReader.close(); + } catch (Exception any) { + } + } + } + } + + if (jarFile != null) { + try { + jarFile.close(); + } catch (Throwable t) {} + } + + baseDirStack.pop(); + + return parsedPage; + } + + /* + * Checks to see if the given URI is matched by a URL pattern specified in + * a jsp-property-group in web.xml, and if so, returns the value of the + * element. + * + * @param absFileName The URI to match + * + * @return The value of the attribute of the + * jsp-property-group with matching URL pattern + */ + private String getJspConfigPageEncoding(String absFileName) + throws JasperException { + + JspConfig jspConfig = ctxt.getOptions().getJspConfig(); + JspConfig.JspProperty jspProperty + = jspConfig.findJspProperty(absFileName); + return jspProperty.getPageEncoding(); + } + + /** + * Determines the syntax (standard or XML) and page encoding properties + * for the given file, and stores them in the 'isXml' and 'sourceEnc' + * instance variables, respectively. + */ + private void determineSyntaxAndEncoding(String absFileName, + JarFile jarFile, + String jspConfigPageEnc) + throws JasperException, IOException { + + isXml = false; + + /* + * 'true' if the syntax (XML or standard) of the file is given + * from external information: either via a JSP configuration element, + * the ".jspx" suffix, or the enclosing file (for included resources) + */ + boolean isExternal = false; + + /* + * Indicates whether we need to revert from temporary usage of + * "ISO-8859-1" back to "UTF-8" + */ + boolean revert = false; + + JspConfig jspConfig = ctxt.getOptions().getJspConfig(); + JspConfig.JspProperty jspProperty = jspConfig.findJspProperty( + absFileName); + if (jspProperty.isXml() != null) { + // If is specified in a , it is used. + isXml = JspUtil.booleanValue(jspProperty.isXml()); + isExternal = true; + } else if (absFileName.endsWith(".jspx") + || absFileName.endsWith(".tagx")) { + isXml = true; + isExternal = true; + } + + if (isExternal && !isXml) { + // JSP (standard) syntax. Use encoding specified in jsp-config + // if provided. + sourceEnc = jspConfigPageEnc; + if (sourceEnc != null) { + return; + } + // We don't know the encoding, so use BOM to determine it + sourceEnc = "ISO-8859-1"; + } else { + // XML syntax or unknown, (auto)detect encoding ... + Object[] ret = XMLEncodingDetector.getEncoding(absFileName, + jarFile, ctxt, err); + sourceEnc = (String) ret[0]; + if (((Boolean) ret[1]).booleanValue()) { + isEncodingSpecifiedInProlog = true; + } + if (((Boolean) ret[2]).booleanValue()) { + isBomPresent = true; + } + skip = ((Integer) ret[3]).intValue(); + + if (!isXml && sourceEnc.equals("UTF-8")) { + /* + * We don't know if we're dealing with XML or standard syntax. + * Therefore, we need to check to see if the page contains + * a element. + * + * We need to be careful, because the page may be encoded in + * ISO-8859-1 (or something entirely different), and may + * contain byte sequences that will cause a UTF-8 converter to + * throw exceptions. + * + * It is safe to use a source encoding of ISO-8859-1 in this + * case, as there are no invalid byte sequences in ISO-8859-1, + * and the byte/character sequences we're looking for (i.e., + * ) are identical in either encoding (both UTF-8 + * and ISO-8859-1 are extensions of ASCII). + */ + sourceEnc = "ISO-8859-1"; + revert = true; + } + } + + if (isXml) { + // (This implies 'isExternal' is TRUE.) + // We know we're dealing with a JSP document (via JSP config or + // ".jspx" suffix), so we're done. + return; + } + + /* + * At this point, 'isExternal' or 'isXml' is FALSE. + * Search for jsp:root action, in order to determine if we're dealing + * with XML or standard syntax (unless we already know what we're + * dealing with, i.e., when 'isExternal' is TRUE and 'isXml' is FALSE). + * No check for XML prolog, since nothing prevents a page from + * outputting XML and still using JSP syntax (in this case, the + * XML prolog is treated as template text). + */ + JspReader jspReader = null; + try { + jspReader = new JspReader(ctxt, absFileName, sourceEnc, jarFile, + err); + } catch (FileNotFoundException ex) { + throw new JasperException(ex); + } + jspReader.setSingleFile(true); + Mark startMark = jspReader.mark(); + if (!isExternal) { + jspReader.reset(startMark); + if (hasJspRoot(jspReader)) { + if (revert) { + sourceEnc = "UTF-8"; + } + isXml = true; + return; + } else { + if (revert && isBomPresent) { + sourceEnc = "UTF-8"; + } + isXml = false; + } + } + + /* + * At this point, we know we're dealing with JSP syntax. + * If an XML prolog is provided, it's treated as template text. + * Determine the page encoding from the page directive, unless it's + * specified via JSP config. + */ + if (!isBomPresent) { + sourceEnc = jspConfigPageEnc; + if (sourceEnc == null) { + sourceEnc = getPageEncodingForJspSyntax(jspReader, startMark); + if (sourceEnc == null) { + // Default to "ISO-8859-1" per JSP spec + sourceEnc = "ISO-8859-1"; + isDefaultPageEncoding = true; + } + } + } + + } + + /* + * Determines page source encoding for page or tag file in JSP syntax, + * by reading (in this order) the value of the 'pageEncoding' page + * directive attribute, or the charset value of the 'contentType' page + * directive attribute. + * + * @return The page encoding, or null if not found + */ + private String getPageEncodingForJspSyntax(JspReader jspReader, + Mark startMark) + throws JasperException { + + String encoding = null; + String saveEncoding = null; + + jspReader.reset(startMark); + + /* + * Determine page encoding from directive of the form <%@ page %>, + * <%@ tag %>, or . + */ + while (true) { + if (jspReader.skipUntil("<") == null) { + break; + } + // If this is a comment, skip until its end + if (jspReader.matches("%--")) { + if (jspReader.skipUntil("--%>") == null) { + // error will be caught in Parser + break; + } + continue; + } + boolean isDirective = jspReader.matches("%@"); + if (isDirective) { + jspReader.skipSpaces(); + } + else { + isDirective = jspReader.matches("jsp:directive."); + } + if (!isDirective) { + continue; + } + + // compare for "tag ", so we don't match "taglib" + if (jspReader.matches("tag ") || jspReader.matches("page")) { + + jspReader.skipSpaces(); + Attributes attrs = Parser.parseAttributes(this, jspReader); + encoding = getPageEncodingFromDirective(attrs, "pageEncoding"); + if (encoding != null) { + break; + } + encoding = getPageEncodingFromDirective(attrs, "contentType"); + if (encoding != null) { + saveEncoding = encoding; + } + } + } + + if (encoding == null) { + encoding = saveEncoding; + } + + return encoding; + } + + /* + * Scans the given attributes for the attribute with the given name, + * which is either 'pageEncoding' or 'contentType', and returns the + * specified page encoding. + * + * In the case of 'contentType', the page encoding is taken from the + * content type's 'charset' component. + * + * @param attrs The page directive attributes + * @param attrName The name of the attribute to search for (either + * 'pageEncoding' or 'contentType') + * + * @return The page encoding, or null + */ + private String getPageEncodingFromDirective(Attributes attrs, + String attrName) { + String value = attrs.getValue(attrName); + if (attrName.equals("pageEncoding")) { + return value; + } + + // attrName = contentType + String contentType = value; + String encoding = null; + if (contentType != null) { + int loc = contentType.indexOf(CHARSET); + if (loc != -1) { + encoding = contentType.substring(loc + CHARSET.length()); + } + } + + return encoding; + } + + /* + * Resolve the name of the file and update baseDirStack() to keep track of + * the current base directory for each included file. + * The 'root' file is always an 'absolute' path, so no need to put an + * initial value in the baseDirStack. + */ + private String resolveFileName(String inFileName) { + String fileName = inFileName.replace('\\', '/'); + boolean isAbsolute = fileName.startsWith("/"); + fileName = isAbsolute ? fileName + : (String) baseDirStack.peek() + fileName; + String baseDir = + fileName.substring(0, fileName.lastIndexOf("/") + 1); + baseDirStack.push(baseDir); + return fileName; + } + + /* + * Checks to see if the given page contains, as its first element, a + * element whose prefix is bound to the JSP namespace, as in: + * + * + * ... + * + * + * @param reader The reader for this page + * + * @return true if this page contains a root element whose prefix is bound + * to the JSP namespace, and false otherwise + */ + private boolean hasJspRoot(JspReader reader) throws JasperException { + + // :root must be the first element + Mark start = null; + while ((start = reader.skipUntil("<")) != null) { + int c = reader.nextChar(); + if (c != '!' && c != '?') break; + } + if (start == null) { + return false; + } + Mark stop = reader.skipUntil(":root"); + if (stop == null) { + return false; + } + // call substring to get rid of leading '<' + String prefix = reader.getText(start, stop).substring(1); + + start = stop; + stop = reader.skipUntil(">"); + if (stop == null) { + return false; + } + + // Determine namespace associated with element's prefix + String root = reader.getText(start, stop); + String xmlnsDecl = "xmlns:" + prefix; + int index = root.indexOf(xmlnsDecl); + if (index == -1) { + return false; + } + index += xmlnsDecl.length(); + while (index < root.length() + && Character.isWhitespace(root.charAt(index))) { + index++; + } + if (index < root.length() && root.charAt(index) == '=') { + index++; + while (index < root.length() + && Character.isWhitespace(root.charAt(index))) { + index++; + } + if (index < root.length() && root.charAt(index++) == '"' + && root.regionMatches(index, JSP_URI, 0, + JSP_URI.length())) { + return true; + } + } + + return false; + } + + private JarFile getJarFile(URL jarFileUrl) throws IOException { + JarFile jarFile = null; + + if (jarFileUrl != null) { + JarURLConnection conn = (JarURLConnection) jarFileUrl.openConnection(); + conn.setUseCaches(false); + conn.connect(); + jarFile = conn.getJarFile(); + } + + return jarFile; + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ScriptingVariabler.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ScriptingVariabler.java new file mode 100644 index 000000000..7110356c9 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ScriptingVariabler.java @@ -0,0 +1,148 @@ +/* + * 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.util.*; +import javax.servlet.jsp.tagext.*; +import org.apache.jasper.JasperException; + +/** + * Class responsible for determining the scripting variables that every + * custom action needs to declare. + * + * @author Jan Luehe + */ +class ScriptingVariabler { + + private static final Integer MAX_SCOPE = new Integer(Integer.MAX_VALUE); + + /* + * Assigns an identifier (of type integer) to every custom tag, in order + * to help identify, for every custom tag, the scripting variables that it + * needs to declare. + */ + static class CustomTagCounter extends Node.Visitor { + + private int count; + private Node.CustomTag parent; + + public void visit(Node.CustomTag n) throws JasperException { + n.setCustomTagParent(parent); + Node.CustomTag tmpParent = parent; + parent = n; + visitBody(n); + parent = tmpParent; + n.setNumCount(new Integer(count++)); + } + } + + /* + * For every custom tag, determines the scripting variables it needs to + * declare. + */ + static class ScriptingVariableVisitor extends Node.Visitor { + + private ErrorDispatcher err; + private Hashtable scriptVars; + + public ScriptingVariableVisitor(ErrorDispatcher err) { + this.err = err; + scriptVars = new Hashtable(); + } + + public void visit(Node.CustomTag n) throws JasperException { + setScriptingVars(n, VariableInfo.AT_BEGIN); + setScriptingVars(n, VariableInfo.NESTED); + visitBody(n); + setScriptingVars(n, VariableInfo.AT_END); + } + + private void setScriptingVars(Node.CustomTag n, int scope) + throws JasperException { + + TagVariableInfo[] tagVarInfos = n.getTagVariableInfos(); + VariableInfo[] varInfos = n.getVariableInfos(); + if (tagVarInfos.length == 0 && varInfos.length == 0) { + return; + } + + Vector vec = new Vector(); + + Integer ownRange = null; + if (scope == VariableInfo.AT_BEGIN + || scope == VariableInfo.AT_END) { + Node.CustomTag parent = n.getCustomTagParent(); + if (parent == null) + ownRange = MAX_SCOPE; + else + ownRange = parent.getNumCount(); + } else { + // NESTED + ownRange = n.getNumCount(); + } + + if (varInfos.length > 0) { + for (int i=0; i 0) { + scriptVars.put(varName, ownRange); + vec.add(varInfos[i]); + } + } + } else { + for (int i=0; i 0) { + scriptVars.put(varName, ownRange); + vec.add(tagVarInfos[i]); + } + } + } + + n.setScriptingVars(vec, scope); + } + } + + public static void set(Node.Nodes page, ErrorDispatcher err) + throws JasperException { + page.visit(new CustomTagCounter()); + page.visit(new ScriptingVariableVisitor(err)); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ServletWriter.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ServletWriter.java new file mode 100644 index 000000000..a407f77db --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ServletWriter.java @@ -0,0 +1,176 @@ +/* + * 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.io.PrintWriter; + +/** + * This is what is used to generate servlets. + * + * @author Anil K. Vijendran + * @author Kin-man Chung + */ +public class ServletWriter { + public static int TAB_WIDTH = 2; + public static String SPACES = " "; + + // Current indent level: + private int indent = 0; + private int virtual_indent = 0; + + // The sink writer: + PrintWriter writer; + + // servlet line numbers start from 1 + private int javaLine = 1; + + + public ServletWriter(PrintWriter writer) { + this.writer = writer; + } + + public void close() throws IOException { + writer.close(); + } + + + // -------------------- Access informations -------------------- + + public int getJavaLine() { + return javaLine; + } + + + // -------------------- Formatting -------------------- + + public void pushIndent() { + virtual_indent += TAB_WIDTH; + if (virtual_indent >= 0 && virtual_indent <= SPACES.length()) + indent = virtual_indent; + } + + public void popIndent() { + virtual_indent -= TAB_WIDTH; + if (virtual_indent >= 0 && virtual_indent <= SPACES.length()) + indent = virtual_indent; + } + + /** + * Print a standard comment for echo outputed chunk. + * @param start The starting position of the JSP chunk being processed. + * @param stop The ending position of the JSP chunk being processed. + */ + public void printComment(Mark start, Mark stop, char[] chars) { + if (start != null && stop != null) { + println("// from="+start); + println("// to="+stop); + } + + if (chars != null) + for(int i = 0; i < chars.length;) { + printin(); + print("// "); + while (chars[i] != '\n' && i < chars.length) + writer.print(chars[i++]); + } + } + + /** + * Prints the given string followed by '\n' + */ + public void println(String s) { + javaLine++; + writer.println(s); + } + + /** + * Prints a '\n' + */ + public void println() { + javaLine++; + writer.println(""); + } + + /** + * Prints the current indention + */ + public void printin() { + writer.print(SPACES.substring(0, indent)); + } + + /** + * Prints the current indention, followed by the given string + */ + public void printin(String s) { + writer.print(SPACES.substring(0, indent)); + writer.print(s); + } + + /** + * Prints the current indention, and then the string, and a '\n'. + */ + public void printil(String s) { + javaLine++; + writer.print(SPACES.substring(0, indent)); + writer.println(s); + } + + /** + * Prints the given char. + * + * Use println() to print a '\n'. + */ + public void print(char c) { + writer.print(c); + } + + /** + * Prints the given int. + */ + public void print(int i) { + writer.print(i); + } + + /** + * Prints the given string. + * + * The string must not contain any '\n', otherwise the line count will be + * off. + */ + public void print(String s) { + writer.print(s); + } + + /** + * Prints the given string. + * + * If the string spans multiple lines, the line count will be adjusted + * accordingly. + */ + public void printMultiLn(String s) { + int index = 0; + + // look for hidden newlines inside strings + while ((index=s.indexOf('\n',index)) > -1 ) { + javaLine++; + index++; + } + + writer.print(s); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapGenerator.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapGenerator.java new file mode 100644 index 000000000..67374478c --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapGenerator.java @@ -0,0 +1,171 @@ +/* + * 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.util.List; +import java.util.ArrayList; + +/** + * Represents a source map (SMAP), which serves to associate lines + * of the input JSP file(s) to lines in the generated servlet in the + * final .class file, according to the JSR-045 spec. + * + * @author Shawn Bayern + */ +public class SmapGenerator { + + //********************************************************************* + // Overview + + /* + * The SMAP syntax is reasonably straightforward. The purpose of this + * class is currently twofold: + * - to provide a simple but low-level Java interface to build + * a logical SMAP + * - to serialize this logical SMAP for eventual inclusion directly + * into a .class file. + */ + + + //********************************************************************* + // Private state + + private String outputFileName; + private String defaultStratum = "Java"; + private List strata = new ArrayList(); + private List embedded = new ArrayList(); + private boolean doEmbedded = true; + + //********************************************************************* + // Methods for adding mapping data + + /** + * Sets the filename (without path information) for the generated + * source file. E.g., "foo$jsp.java". + */ + public synchronized void setOutputFileName(String x) { + outputFileName = x; + } + + /** + * Adds the given SmapStratum object, representing a Stratum with + * logically associated FileSection and LineSection blocks, to + * the current SmapGenerator. If default is true, this + * stratum is made the default stratum, overriding any previously + * set default. + * + * @param stratum the SmapStratum object to add + * @param defaultStratum if true, this SmapStratum is considered + * to represent the default SMAP stratum unless + * overwritten + */ + public synchronized void addStratum(SmapStratum stratum, + boolean defaultStratum) { + strata.add(stratum); + if (defaultStratum) + this.defaultStratum = stratum.getStratumName(); + } + + /** + * Adds the given string as an embedded SMAP with the given stratum name. + * + * @param smap the SMAP to embed + * @param stratumName the name of the stratum output by the compilation + * that produced the smap to be embedded + */ + public synchronized void addSmap(String smap, String stratumName) { + embedded.add("*O " + stratumName + "\n" + + smap + + "*C " + stratumName + "\n"); + } + + /** + * Instructs the SmapGenerator whether to actually print any embedded + * SMAPs or not. Intended for situations without an SMAP resolver. + * + * @param status If false, ignore any embedded SMAPs. + */ + public void setDoEmbedded(boolean status) { + doEmbedded = status; + } + + //********************************************************************* + // Methods for serializing the logical SMAP + + public synchronized String getString() { + // check state and initialize buffer + if (outputFileName == null) + throw new IllegalStateException(); + StringBuffer out = new StringBuffer(); + + // start the SMAP + out.append("SMAP\n"); + out.append(outputFileName + '\n'); + out.append(defaultStratum + '\n'); + + // include embedded SMAPs + if (doEmbedded) { + int nEmbedded = embedded.size(); + for (int i = 0; i < nEmbedded; i++) { + out.append(embedded.get(i)); + } + } + + // print our StratumSections, FileSections, and LineSections + int nStrata = strata.size(); + for (int i = 0; i < nStrata; i++) { + SmapStratum s = (SmapStratum) strata.get(i); + out.append(s.getString()); + } + + // end the SMAP + out.append("*E\n"); + + return out.toString(); + } + + public String toString() { return getString(); } + + //********************************************************************* + // For testing (and as an example of use)... + + public static void main(String args[]) { + SmapGenerator g = new SmapGenerator(); + g.setOutputFileName("foo.java"); + SmapStratum s = new SmapStratum("JSP"); + s.addFile("foo.jsp"); + s.addFile("bar.jsp", "/foo/foo/bar.jsp"); + s.addLineData(1, "foo.jsp", 1, 1, 1); + s.addLineData(2, "foo.jsp", 1, 6, 1); + s.addLineData(3, "foo.jsp", 2, 10, 5); + s.addLineData(20, "bar.jsp", 1, 30, 1); + g.addStratum(s, true); + System.out.print(g); + + System.out.println("---"); + + SmapGenerator embedded = new SmapGenerator(); + embedded.setOutputFileName("blargh.tier2"); + s = new SmapStratum("Tier2"); + s.addFile("1.tier2"); + s.addLineData(1, "1.tier2", 1, 1, 1); + embedded.addStratum(s, true); + g.addSmap(embedded.toString(), "JSP"); + System.out.println(g); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapStratum.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapStratum.java new file mode 100644 index 000000000..d0c506e1a --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapStratum.java @@ -0,0 +1,336 @@ +/* + * 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.util.List; +import java.util.ArrayList; + +/** + * Represents the line and file mappings associated with a JSR-045 + * "stratum". + * + * @author Jayson Falkner + * @author Shawn Bayern + */ +public class SmapStratum { + + //********************************************************************* + // Class for storing LineInfo data + + /** + * Represents a single LineSection in an SMAP, associated with + * a particular stratum. + */ + public static class LineInfo { + private int inputStartLine = -1; + private int outputStartLine = -1; + private int lineFileID = 0; + private int inputLineCount = 1; + private int outputLineIncrement = 1; + private boolean lineFileIDSet = false; + + /** Sets InputStartLine. */ + public void setInputStartLine(int inputStartLine) { + if (inputStartLine < 0) + throw new IllegalArgumentException("" + inputStartLine); + this.inputStartLine = inputStartLine; + } + + /** Sets OutputStartLine. */ + public void setOutputStartLine(int outputStartLine) { + if (outputStartLine < 0) + throw new IllegalArgumentException("" + outputStartLine); + this.outputStartLine = outputStartLine; + } + + /** + * Sets lineFileID. Should be called only when different from + * that of prior LineInfo object (in any given context) or 0 + * if the current LineInfo has no (logical) predecessor. + * LineInfo will print this file number no matter what. + */ + public void setLineFileID(int lineFileID) { + if (lineFileID < 0) + throw new IllegalArgumentException("" + lineFileID); + this.lineFileID = lineFileID; + this.lineFileIDSet = true; + } + + /** Sets InputLineCount. */ + public void setInputLineCount(int inputLineCount) { + if (inputLineCount < 0) + throw new IllegalArgumentException("" + inputLineCount); + this.inputLineCount = inputLineCount; + } + + /** Sets OutputLineIncrement. */ + public void setOutputLineIncrement(int outputLineIncrement) { + if (outputLineIncrement < 0) + throw new IllegalArgumentException("" + outputLineIncrement); + this.outputLineIncrement = outputLineIncrement; + } + + /** + * Retrieves the current LineInfo as a String, print all values + * only when appropriate (but LineInfoID if and only if it's been + * specified, as its necessity is sensitive to context). + */ + public String getString() { + if (inputStartLine == -1 || outputStartLine == -1) + throw new IllegalStateException(); + StringBuffer out = new StringBuffer(); + out.append(inputStartLine); + if (lineFileIDSet) + out.append("#" + lineFileID); + if (inputLineCount != 1) + out.append("," + inputLineCount); + out.append(":" + outputStartLine); + if (outputLineIncrement != 1) + out.append("," + outputLineIncrement); + out.append('\n'); + return out.toString(); + } + + public String toString() { + return getString(); + } + } + + //********************************************************************* + // Private state + + private String stratumName; + private List fileNameList; + private List filePathList; + private List lineData; + private int lastFileID; + + //********************************************************************* + // Constructor + + /** + * Constructs a new SmapStratum object for the given stratum name + * (e.g., JSP). + * + * @param stratumName the name of the stratum (e.g., JSP) + */ + public SmapStratum(String stratumName) { + this.stratumName = stratumName; + fileNameList = new ArrayList(); + filePathList = new ArrayList(); + lineData = new ArrayList(); + lastFileID = 0; + } + + //********************************************************************* + // Methods to add mapping information + + /** + * Adds record of a new file, by filename. + * + * @param filename the filename to add, unqualified by path. + */ + public void addFile(String filename) { + addFile(filename, filename); + } + + /** + * Adds record of a new file, by filename and path. The path + * may be relative to a source compilation path. + * + * @param filename the filename to add, unqualified by path + * @param filePath the path for the filename, potentially relative + * to a source compilation path + */ + public void addFile(String filename, String filePath) { + int pathIndex = filePathList.indexOf(filePath); + if (pathIndex == -1) { + fileNameList.add(filename); + filePathList.add(filePath); + } + } + + /** + * Combines consecutive LineInfos wherever possible + */ + public void optimizeLineSection() { + +/* Some debugging code + for (int i = 0; i < lineData.size(); i++) { + LineInfo li = (LineInfo)lineData.get(i); + System.out.print(li.toString()); + } +*/ + //Incorporate each LineInfo into the previous LineInfo's + //outputLineIncrement, if possible + int i = 0; + while (i < lineData.size() - 1) { + LineInfo li = (LineInfo)lineData.get(i); + LineInfo liNext = (LineInfo)lineData.get(i + 1); + if (!liNext.lineFileIDSet + && liNext.inputStartLine == li.inputStartLine + && liNext.inputLineCount == 1 + && li.inputLineCount == 1 + && liNext.outputStartLine + == li.outputStartLine + + li.inputLineCount * li.outputLineIncrement) { + li.setOutputLineIncrement( + liNext.outputStartLine + - li.outputStartLine + + liNext.outputLineIncrement); + lineData.remove(i + 1); + } else { + i++; + } + } + + //Incorporate each LineInfo into the previous LineInfo's + //inputLineCount, if possible + i = 0; + while (i < lineData.size() - 1) { + LineInfo li = (LineInfo)lineData.get(i); + LineInfo liNext = (LineInfo)lineData.get(i + 1); + if (!liNext.lineFileIDSet + && liNext.inputStartLine == li.inputStartLine + li.inputLineCount + && liNext.outputLineIncrement == li.outputLineIncrement + && liNext.outputStartLine + == li.outputStartLine + + li.inputLineCount * li.outputLineIncrement) { + li.setInputLineCount(li.inputLineCount + liNext.inputLineCount); + lineData.remove(i + 1); + } else { + i++; + } + } + } + + /** + * Adds complete information about a simple line mapping. Specify + * all the fields in this method; the back-end machinery takes care + * of printing only those that are necessary in the final SMAP. + * (My view is that fields are optional primarily for spatial efficiency, + * not for programmer convenience. Could always add utility methods + * later.) + * + * @param inputStartLine starting line in the source file + * (SMAP InputStartLine) + * @param inputFileName the filepath (or name) from which the input comes + * (yields SMAP LineFileID) Use unqualified names + * carefully, and only when they uniquely identify a file. + * @param inputLineCount the number of lines in the input to map + * (SMAP LineFileCount) + * @param outputStartLine starting line in the output file + * (SMAP OutputStartLine) + * @param outputLineIncrement number of output lines to map to each + * input line (SMAP OutputLineIncrement). Given the + * fact that the name starts with "output", I continuously have + * the subconscious urge to call this field + * OutputLineExcrement. + */ + public void addLineData( + int inputStartLine, + String inputFileName, + int inputLineCount, + int outputStartLine, + int outputLineIncrement) { + // check the input - what are you doing here?? + int fileIndex = filePathList.indexOf(inputFileName); + if (fileIndex == -1) // still + throw new IllegalArgumentException( + "inputFileName: " + inputFileName); + + //Jasper incorrectly SMAPs certain Nodes, giving them an + //outputStartLine of 0. This can cause a fatal error in + //optimizeLineSection, making it impossible for Jasper to + //compile the JSP. Until we can fix the underlying + //SMAPping problem, we simply ignore the flawed SMAP entries. + if (outputStartLine == 0) + return; + + // build the LineInfo + LineInfo li = new LineInfo(); + li.setInputStartLine(inputStartLine); + li.setInputLineCount(inputLineCount); + li.setOutputStartLine(outputStartLine); + li.setOutputLineIncrement(outputLineIncrement); + if (fileIndex != lastFileID) + li.setLineFileID(fileIndex); + lastFileID = fileIndex; + + // save it + lineData.add(li); + } + + //********************************************************************* + // Methods to retrieve information + + /** + * Returns the name of the stratum. + */ + public String getStratumName() { + return stratumName; + } + + /** + * Returns the given stratum as a String: a StratumSection, + * followed by at least one FileSection and at least one LineSection. + */ + public String getString() { + // check state and initialize buffer + if (fileNameList.size() == 0 || lineData.size() == 0) + return null; + + StringBuffer out = new StringBuffer(); + + // print StratumSection + out.append("*S " + stratumName + "\n"); + + // print FileSection + out.append("*F\n"); + int bound = fileNameList.size(); + for (int i = 0; i < bound; i++) { + if (filePathList.get(i) != null) { + out.append("+ " + i + " " + fileNameList.get(i) + "\n"); + // Source paths must be relative, not absolute, so we + // remove the leading "/", if one exists. + String filePath = (String)filePathList.get(i); + if (filePath.startsWith("/")) { + filePath = filePath.substring(1); + } + out.append(filePath + "\n"); + } else { + out.append(i + " " + fileNameList.get(i) + "\n"); + } + } + + // print LineSection + out.append("*L\n"); + bound = lineData.size(); + for (int i = 0; i < bound; i++) { + LineInfo li = (LineInfo)lineData.get(i); + out.append(li.getString()); + } + + return out.toString(); + } + + public String toString() { + return getString(); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapUtil.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapUtil.java new file mode 100644 index 000000000..c9e08ec08 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapUtil.java @@ -0,0 +1,729 @@ +/* + * 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.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.jasper.JasperException; +import org.apache.jasper.JspCompilationContext; + +/** + * Contains static utilities for generating SMAP data based on the + * current version of Jasper. + * + * @author Jayson Falkner + * @author Shawn Bayern + * @author Robert Field (inner SDEInstaller class) + * @author Mark Roth + * @author Kin-man Chung + */ +public class SmapUtil { + + private org.apache.juli.logging.Log log= + org.apache.juli.logging.LogFactory.getLog( SmapUtil.class ); + + //********************************************************************* + // Constants + + public static final String SMAP_ENCODING = "UTF-8"; + + //********************************************************************* + // Public entry points + + /** + * Generates an appropriate SMAP representing the current compilation + * context. (JSR-045.) + * + * @param ctxt Current compilation context + * @param pageNodes The current JSP page + * @return a SMAP for the page + */ + public static String[] generateSmap( + JspCompilationContext ctxt, + Node.Nodes pageNodes) + throws IOException { + + // Scan the nodes for presence of Jasper generated inner classes + PreScanVisitor psVisitor = new PreScanVisitor(); + try { + pageNodes.visit(psVisitor); + } catch (JasperException ex) { + } + HashMap map = psVisitor.getMap(); + + // set up our SMAP generator + SmapGenerator g = new SmapGenerator(); + + /** Disable reading of input SMAP because: + 1. There is a bug here: getRealPath() is null if .jsp is in a jar + Bugzilla 14660. + 2. Mappings from other sources into .jsp files are not supported. + TODO: fix 1. if 2. is not true. + // determine if we have an input SMAP + String smapPath = inputSmapPath(ctxt.getRealPath(ctxt.getJspFile())); + File inputSmap = new File(smapPath); + if (inputSmap.exists()) { + byte[] embeddedSmap = null; + byte[] subSmap = SDEInstaller.readWhole(inputSmap); + String subSmapString = new String(subSmap, SMAP_ENCODING); + g.addSmap(subSmapString, "JSP"); + } + **/ + + // now, assemble info about our own stratum (JSP) using JspLineMap + SmapStratum s = new SmapStratum("JSP"); + + g.setOutputFileName(unqualify(ctxt.getServletJavaFileName())); + + // Map out Node.Nodes + evaluateNodes(pageNodes, s, map, ctxt.getOptions().getMappedFile()); + s.optimizeLineSection(); + g.addStratum(s, true); + + if (ctxt.getOptions().isSmapDumped()) { + File outSmap = new File(ctxt.getClassFileName() + ".smap"); + PrintWriter so = + new PrintWriter( + new OutputStreamWriter( + new FileOutputStream(outSmap), + SMAP_ENCODING)); + so.print(g.getString()); + so.close(); + } + + String classFileName = ctxt.getClassFileName(); + int innerClassCount = map.size(); + String [] smapInfo = new String[2 + innerClassCount*2]; + smapInfo[0] = classFileName; + smapInfo[1] = g.getString(); + + int count = 2; + Iterator iter = map.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry) iter.next(); + String innerClass = (String) entry.getKey(); + s = (SmapStratum) entry.getValue(); + s.optimizeLineSection(); + g = new SmapGenerator(); + g.setOutputFileName(unqualify(ctxt.getServletJavaFileName())); + g.addStratum(s, true); + + String innerClassFileName = + classFileName.substring(0, classFileName.indexOf(".class")) + + '$' + innerClass + ".class"; + if (ctxt.getOptions().isSmapDumped()) { + File outSmap = new File(innerClassFileName + ".smap"); + PrintWriter so = + new PrintWriter( + new OutputStreamWriter( + new FileOutputStream(outSmap), + SMAP_ENCODING)); + so.print(g.getString()); + so.close(); + } + smapInfo[count] = innerClassFileName; + smapInfo[count+1] = g.getString(); + count += 2; + } + + return smapInfo; + } + + public static void installSmap(String[] smap) + throws IOException { + if (smap == null) { + return; + } + + for (int i = 0; i < smap.length; i += 2) { + File outServlet = new File(smap[i]); + SDEInstaller.install(outServlet, smap[i+1].getBytes()); + } + } + + //********************************************************************* + // Private utilities + + /** + * Returns an unqualified version of the given file path. + */ + private static String unqualify(String path) { + path = path.replace('\\', '/'); + return path.substring(path.lastIndexOf('/') + 1); + } + + /** + * Returns a file path corresponding to a potential SMAP input + * for the given compilation input (JSP file). + */ + private static String inputSmapPath(String path) { + return path.substring(0, path.lastIndexOf('.') + 1) + "smap"; + } + + //********************************************************************* + // Installation logic (from Robert Field, JSR-045 spec lead) + private static class SDEInstaller { + + private org.apache.juli.logging.Log log= + org.apache.juli.logging.LogFactory.getLog( SDEInstaller.class ); + + static final String nameSDE = "SourceDebugExtension"; + + byte[] orig; + byte[] sdeAttr; + byte[] gen; + + int origPos = 0; + int genPos = 0; + + int sdeIndex; + + public static void main(String[] args) throws IOException { + if (args.length == 2) { + install(new File(args[0]), new File(args[1])); + } else if (args.length == 3) { + install( + new File(args[0]), + new File(args[1]), + new File(args[2])); + } else { + System.err.println( + "Usage: " + + " \n" + + " "); + } + } + + static void install(File inClassFile, File attrFile, File outClassFile) + throws IOException { + new SDEInstaller(inClassFile, attrFile, outClassFile); + } + + static void install(File inOutClassFile, File attrFile) + throws IOException { + File tmpFile = new File(inOutClassFile.getPath() + "tmp"); + new SDEInstaller(inOutClassFile, attrFile, tmpFile); + if (!inOutClassFile.delete()) { + throw new IOException("inOutClassFile.delete() failed"); + } + if (!tmpFile.renameTo(inOutClassFile)) { + throw new IOException("tmpFile.renameTo(inOutClassFile) failed"); + } + } + + static void install(File classFile, byte[] smap) throws IOException { + File tmpFile = new File(classFile.getPath() + "tmp"); + new SDEInstaller(classFile, smap, tmpFile); + if (!classFile.delete()) { + throw new IOException("classFile.delete() failed"); + } + if (!tmpFile.renameTo(classFile)) { + throw new IOException("tmpFile.renameTo(classFile) failed"); + } + } + + SDEInstaller(File inClassFile, byte[] sdeAttr, File outClassFile) + throws IOException { + if (!inClassFile.exists()) { + throw new FileNotFoundException("no such file: " + inClassFile); + } + + this.sdeAttr = sdeAttr; + // get the bytes + orig = readWhole(inClassFile); + gen = new byte[orig.length + sdeAttr.length + 100]; + + // do it + addSDE(); + + // write result + FileOutputStream outStream = new FileOutputStream(outClassFile); + outStream.write(gen, 0, genPos); + outStream.close(); + } + + SDEInstaller(File inClassFile, File attrFile, File outClassFile) + throws IOException { + this(inClassFile, readWhole(attrFile), outClassFile); + } + + static byte[] readWhole(File input) throws IOException { + FileInputStream inStream = new FileInputStream(input); + int len = (int)input.length(); + byte[] bytes = new byte[len]; + if (inStream.read(bytes, 0, len) != len) { + throw new IOException("expected size: " + len); + } + inStream.close(); + return bytes; + } + + void addSDE() throws UnsupportedEncodingException, IOException { + int i; + copy(4 + 2 + 2); // magic min/maj version + int constantPoolCountPos = genPos; + int constantPoolCount = readU2(); + if (log.isDebugEnabled()) + log.debug("constant pool count: " + constantPoolCount); + writeU2(constantPoolCount); + + // copy old constant pool return index of SDE symbol, if found + sdeIndex = copyConstantPool(constantPoolCount); + if (sdeIndex < 0) { + // if "SourceDebugExtension" symbol not there add it + writeUtf8ForSDE(); + + // increment the countantPoolCount + sdeIndex = constantPoolCount; + ++constantPoolCount; + randomAccessWriteU2(constantPoolCountPos, constantPoolCount); + + if (log.isDebugEnabled()) + log.debug("SourceDebugExtension not found, installed at: " + sdeIndex); + } else { + if (log.isDebugEnabled()) + log.debug("SourceDebugExtension found at: " + sdeIndex); + } + copy(2 + 2 + 2); // access, this, super + int interfaceCount = readU2(); + writeU2(interfaceCount); + if (log.isDebugEnabled()) + log.debug("interfaceCount: " + interfaceCount); + copy(interfaceCount * 2); + copyMembers(); // fields + copyMembers(); // methods + int attrCountPos = genPos; + int attrCount = readU2(); + writeU2(attrCount); + if (log.isDebugEnabled()) + log.debug("class attrCount: " + attrCount); + // copy the class attributes, return true if SDE attr found (not copied) + if (!copyAttrs(attrCount)) { + // we will be adding SDE and it isn't already counted + ++attrCount; + randomAccessWriteU2(attrCountPos, attrCount); + if (log.isDebugEnabled()) + log.debug("class attrCount incremented"); + } + writeAttrForSDE(sdeIndex); + } + + void copyMembers() { + int count = readU2(); + writeU2(count); + if (log.isDebugEnabled()) + log.debug("members count: " + count); + for (int i = 0; i < count; ++i) { + copy(6); // access, name, descriptor + int attrCount = readU2(); + writeU2(attrCount); + if (log.isDebugEnabled()) + log.debug("member attr count: " + attrCount); + copyAttrs(attrCount); + } + } + + boolean copyAttrs(int attrCount) { + boolean sdeFound = false; + for (int i = 0; i < attrCount; ++i) { + int nameIndex = readU2(); + // don't write old SDE + if (nameIndex == sdeIndex) { + sdeFound = true; + if (log.isDebugEnabled()) + log.debug("SDE attr found"); + } else { + writeU2(nameIndex); // name + int len = readU4(); + writeU4(len); + copy(len); + if (log.isDebugEnabled()) + log.debug("attr len: " + len); + } + } + return sdeFound; + } + + void writeAttrForSDE(int index) { + writeU2(index); + writeU4(sdeAttr.length); + for (int i = 0; i < sdeAttr.length; ++i) { + writeU1(sdeAttr[i]); + } + } + + void randomAccessWriteU2(int pos, int val) { + int savePos = genPos; + genPos = pos; + writeU2(val); + genPos = savePos; + } + + int readU1() { + return ((int)orig[origPos++]) & 0xFF; + } + + int readU2() { + int res = readU1(); + return (res << 8) + readU1(); + } + + int readU4() { + int res = readU2(); + return (res << 16) + readU2(); + } + + void writeU1(int val) { + gen[genPos++] = (byte)val; + } + + void writeU2(int val) { + writeU1(val >> 8); + writeU1(val & 0xFF); + } + + void writeU4(int val) { + writeU2(val >> 16); + writeU2(val & 0xFFFF); + } + + void copy(int count) { + for (int i = 0; i < count; ++i) { + gen[genPos++] = orig[origPos++]; + } + } + + byte[] readBytes(int count) { + byte[] bytes = new byte[count]; + for (int i = 0; i < count; ++i) { + bytes[i] = orig[origPos++]; + } + return bytes; + } + + void writeBytes(byte[] bytes) { + for (int i = 0; i < bytes.length; ++i) { + gen[genPos++] = bytes[i]; + } + } + + int copyConstantPool(int constantPoolCount) + throws UnsupportedEncodingException, IOException { + int sdeIndex = -1; + // copy const pool index zero not in class file + for (int i = 1; i < constantPoolCount; ++i) { + int tag = readU1(); + writeU1(tag); + switch (tag) { + case 7 : // Class + case 8 : // String + if (log.isDebugEnabled()) + log.debug(i + " copying 2 bytes"); + copy(2); + break; + case 9 : // Field + case 10 : // Method + case 11 : // InterfaceMethod + case 3 : // Integer + case 4 : // Float + case 12 : // NameAndType + if (log.isDebugEnabled()) + log.debug(i + " copying 4 bytes"); + copy(4); + break; + case 5 : // Long + case 6 : // Double + if (log.isDebugEnabled()) + log.debug(i + " copying 8 bytes"); + copy(8); + i++; + break; + case 1 : // Utf8 + int len = readU2(); + writeU2(len); + byte[] utf8 = readBytes(len); + String str = new String(utf8, "UTF-8"); + if (log.isDebugEnabled()) + log.debug(i + " read class attr -- '" + str + "'"); + if (str.equals(nameSDE)) { + sdeIndex = i; + } + writeBytes(utf8); + break; + default : + throw new IOException("unexpected tag: " + tag); + } + } + return sdeIndex; + } + + void writeUtf8ForSDE() { + int len = nameSDE.length(); + writeU1(1); // Utf8 tag + writeU2(len); + for (int i = 0; i < len; ++i) { + writeU1(nameSDE.charAt(i)); + } + } + } + + public static void evaluateNodes( + Node.Nodes nodes, + SmapStratum s, + HashMap innerClassMap, + boolean breakAtLF) { + try { + nodes.visit(new SmapGenVisitor(s, breakAtLF, innerClassMap)); + } catch (JasperException ex) { + } + } + + static class SmapGenVisitor extends Node.Visitor { + + private SmapStratum smap; + private boolean breakAtLF; + private HashMap innerClassMap; + + SmapGenVisitor(SmapStratum s, boolean breakAtLF, HashMap map) { + this.smap = s; + this.breakAtLF = breakAtLF; + this.innerClassMap = map; + } + + public void visitBody(Node n) throws JasperException { + SmapStratum smapSave = smap; + String innerClass = n.getInnerClassName(); + if (innerClass != null) { + this.smap = (SmapStratum) innerClassMap.get(innerClass); + } + super.visitBody(n); + smap = smapSave; + } + + public void visit(Node.Declaration n) throws JasperException { + doSmapText(n); + } + + public void visit(Node.Expression n) throws JasperException { + doSmapText(n); + } + + public void visit(Node.Scriptlet n) throws JasperException { + doSmapText(n); + } + + public void visit(Node.IncludeAction n) throws JasperException { + doSmap(n); + visitBody(n); + } + + public void visit(Node.ForwardAction n) throws JasperException { + doSmap(n); + visitBody(n); + } + + public void visit(Node.GetProperty n) throws JasperException { + doSmap(n); + visitBody(n); + } + + public void visit(Node.SetProperty n) throws JasperException { + doSmap(n); + visitBody(n); + } + + public void visit(Node.UseBean n) throws JasperException { + doSmap(n); + visitBody(n); + } + + public void visit(Node.PlugIn n) throws JasperException { + doSmap(n); + visitBody(n); + } + + public void visit(Node.CustomTag n) throws JasperException { + doSmap(n); + visitBody(n); + } + + public void visit(Node.UninterpretedTag n) throws JasperException { + doSmap(n); + visitBody(n); + } + + public void visit(Node.JspElement n) throws JasperException { + doSmap(n); + visitBody(n); + } + + public void visit(Node.JspText n) throws JasperException { + doSmap(n); + visitBody(n); + } + + public void visit(Node.NamedAttribute n) throws JasperException { + visitBody(n); + } + + public void visit(Node.JspBody n) throws JasperException { + doSmap(n); + visitBody(n); + } + + public void visit(Node.InvokeAction n) throws JasperException { + doSmap(n); + visitBody(n); + } + + public void visit(Node.DoBodyAction n) throws JasperException { + doSmap(n); + visitBody(n); + } + + public void visit(Node.ELExpression n) throws JasperException { + doSmap(n); + } + + public void visit(Node.TemplateText n) throws JasperException { + Mark mark = n.getStart(); + if (mark == null) { + return; + } + + //Add the file information + String fileName = mark.getFile(); + smap.addFile(unqualify(fileName), fileName); + + //Add a LineInfo that corresponds to the beginning of this node + int iInputStartLine = mark.getLineNumber(); + int iOutputStartLine = n.getBeginJavaLine(); + int iOutputLineIncrement = breakAtLF? 1: 0; + smap.addLineData(iInputStartLine, fileName, 1, iOutputStartLine, + iOutputLineIncrement); + + // Output additional mappings in the text + java.util.ArrayList extraSmap = n.getExtraSmap(); + + if (extraSmap != null) { + for (int i = 0; i < extraSmap.size(); i++) { + iOutputStartLine += iOutputLineIncrement; + smap.addLineData( + iInputStartLine+((Integer)extraSmap.get(i)).intValue(), + fileName, + 1, + iOutputStartLine, + iOutputLineIncrement); + } + } + } + + private void doSmap( + Node n, + int inLineCount, + int outIncrement, + int skippedLines) { + Mark mark = n.getStart(); + if (mark == null) { + return; + } + + String unqualifiedName = unqualify(mark.getFile()); + smap.addFile(unqualifiedName, mark.getFile()); + smap.addLineData( + mark.getLineNumber() + skippedLines, + mark.getFile(), + inLineCount - skippedLines, + n.getBeginJavaLine() + skippedLines, + outIncrement); + } + + private void doSmap(Node n) { + doSmap(n, 1, n.getEndJavaLine() - n.getBeginJavaLine(), 0); + } + + private void doSmapText(Node n) { + String text = n.getText(); + int index = 0; + int next = 0; + int lineCount = 1; + int skippedLines = 0; + boolean slashStarSeen = false; + boolean beginning = true; + + // Count lines inside text, but skipping comment lines at the + // beginning of the text. + while ((next = text.indexOf('\n', index)) > -1) { + if (beginning) { + String line = text.substring(index, next).trim(); + if (!slashStarSeen && line.startsWith("/*")) { + slashStarSeen = true; + } + if (slashStarSeen) { + skippedLines++; + int endIndex = line.indexOf("*/"); + if (endIndex >= 0) { + // End of /* */ comment + slashStarSeen = false; + if (endIndex < line.length() - 2) { + // Some executable code after comment + skippedLines--; + beginning = false; + } + } + } else if (line.length() == 0 || line.startsWith("//")) { + skippedLines++; + } else { + beginning = false; + } + } + lineCount++; + index = next + 1; + } + + doSmap(n, lineCount, 1, skippedLines); + } + } + + private static class PreScanVisitor extends Node.Visitor { + + HashMap map = new HashMap(); + + public void doVisit(Node n) { + String inner = n.getInnerClassName(); + if (inner != null && !map.containsKey(inner)) { + map.put(inner, new SmapStratum("JSP")); + } + } + + HashMap getMap() { + return map; + } + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagConstants.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagConstants.java new file mode 100644 index 000000000..373eb0e9d --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagConstants.java @@ -0,0 +1,116 @@ +/* + * 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; + +public interface TagConstants { + + public static final String JSP_URI = "http://java.sun.com/JSP/Page"; + + public static final String DIRECTIVE_ACTION = "directive."; + + public static final String ROOT_ACTION = "root"; + public static final String JSP_ROOT_ACTION = "jsp:root"; + + public static final String PAGE_DIRECTIVE_ACTION = "directive.page"; + public static final String JSP_PAGE_DIRECTIVE_ACTION = "jsp:directive.page"; + + public static final String INCLUDE_DIRECTIVE_ACTION = "directive.include"; + public static final String JSP_INCLUDE_DIRECTIVE_ACTION = "jsp:directive.include"; + + public static final String DECLARATION_ACTION = "declaration"; + public static final String JSP_DECLARATION_ACTION = "jsp:declaration"; + + public static final String SCRIPTLET_ACTION = "scriptlet"; + public static final String JSP_SCRIPTLET_ACTION = "jsp:scriptlet"; + + public static final String EXPRESSION_ACTION = "expression"; + public static final String JSP_EXPRESSION_ACTION = "jsp:expression"; + + public static final String USE_BEAN_ACTION = "useBean"; + public static final String JSP_USE_BEAN_ACTION = "jsp:useBean"; + + public static final String SET_PROPERTY_ACTION = "setProperty"; + public static final String JSP_SET_PROPERTY_ACTION = "jsp:setProperty"; + + public static final String GET_PROPERTY_ACTION = "getProperty"; + public static final String JSP_GET_PROPERTY_ACTION = "jsp:getProperty"; + + public static final String INCLUDE_ACTION = "include"; + public static final String JSP_INCLUDE_ACTION = "jsp:include"; + + public static final String FORWARD_ACTION = "forward"; + public static final String JSP_FORWARD_ACTION = "jsp:forward"; + + public static final String PARAM_ACTION = "param"; + public static final String JSP_PARAM_ACTION = "jsp:param"; + + public static final String PARAMS_ACTION = "params"; + public static final String JSP_PARAMS_ACTION = "jsp:params"; + + public static final String PLUGIN_ACTION = "plugin"; + public static final String JSP_PLUGIN_ACTION = "jsp:plugin"; + + public static final String FALLBACK_ACTION = "fallback"; + public static final String JSP_FALLBACK_ACTION = "jsp:fallback"; + + public static final String TEXT_ACTION = "text"; + public static final String JSP_TEXT_ACTION = "jsp:text"; + public static final String JSP_TEXT_ACTION_END = ""; + + public static final String ATTRIBUTE_ACTION = "attribute"; + public static final String JSP_ATTRIBUTE_ACTION = "jsp:attribute"; + + public static final String BODY_ACTION = "body"; + public static final String JSP_BODY_ACTION = "jsp:body"; + + public static final String ELEMENT_ACTION = "element"; + public static final String JSP_ELEMENT_ACTION = "jsp:element"; + + public static final String OUTPUT_ACTION = "output"; + public static final String JSP_OUTPUT_ACTION = "jsp:output"; + + public static final String TAGLIB_DIRECTIVE_ACTION = "taglib"; + public static final String JSP_TAGLIB_DIRECTIVE_ACTION = "jsp:taglib"; + + /* + * Tag Files + */ + public static final String INVOKE_ACTION = "invoke"; + public static final String JSP_INVOKE_ACTION = "jsp:invoke"; + + public static final String DOBODY_ACTION = "doBody"; + public static final String JSP_DOBODY_ACTION = "jsp:doBody"; + + /* + * Tag File Directives + */ + public static final String TAG_DIRECTIVE_ACTION = "directive.tag"; + public static final String JSP_TAG_DIRECTIVE_ACTION = "jsp:directive.tag"; + + public static final String ATTRIBUTE_DIRECTIVE_ACTION = "directive.attribute"; + public static final String JSP_ATTRIBUTE_DIRECTIVE_ACTION = "jsp:directive.attribute"; + + public static final String VARIABLE_DIRECTIVE_ACTION = "directive.variable"; + public static final String JSP_VARIABLE_DIRECTIVE_ACTION = "jsp:directive.variable"; + + /* + * Directive attributes + */ + public static final String URN_JSPTAGDIR = "urn:jsptagdir:"; + public static final String URN_JSPTLD = "urn:jsptld:"; +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagFileProcessor.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagFileProcessor.java new file mode 100644 index 000000000..d758bc0af --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagFileProcessor.java @@ -0,0 +1,726 @@ +/* + * 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.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; +import java.util.HashMap; + +import javax.el.MethodExpression; +import javax.el.ValueExpression; +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.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("deferredSyntaxAllowedAsLiteral"), // JSP 2.1 + new JspUtil.ValidAttribute("trimDirectiveWhitespaces"), // JSP 2.1 + 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("deferredValue"), // JSP 2.1 + new JspUtil.ValidAttribute("deferredValueType"), // JSP 2.1 + new JspUtil.ValidAttribute("deferredMethod"), // JSP 2 + new JspUtil.ValidAttribute("deferredMethodSignature"), // JSP 21 + 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); + + // JSP 2.1 Table JSP.8-3 + // handle deferredValue and deferredValueType + boolean deferredValue = false; + boolean deferredValueSpecified = false; + String deferredValueString = n.getAttributeValue("deferredValue"); + if (deferredValueString != null) { + deferredValueSpecified = true; + deferredValue = JspUtil.booleanValue(deferredValueString); + } + String deferredValueType = n.getAttributeValue("deferredValueType"); + if (deferredValueType != null) { + if (deferredValueSpecified && !deferredValue) { + err.jspError(n, "jsp.error.deferredvaluetypewithoutdeferredvalue"); + } else { + deferredValue = true; + } + } else if (deferredValue) { + deferredValueType = "java.lang.Object"; + } else { + deferredValueType = "java.lang.String"; + } + + // JSP 2.1 Table JSP.8-3 + // handle deferredMethod and deferredMethodSignature + boolean deferredMethod = false; + boolean deferredMethodSpecified = false; + String deferredMethodString = n.getAttributeValue("deferredMethod"); + if (deferredMethodString != null) { + deferredMethodSpecified = true; + deferredMethod = JspUtil.booleanValue(deferredMethodString); + } + String deferredMethodSignature = n + .getAttributeValue("deferredMethodSignature"); + if (deferredMethodSignature != null) { + if (deferredMethodSpecified && !deferredMethod) { + err.jspError(n, "jsp.error.deferredmethodsignaturewithoutdeferredmethod"); + } else { + deferredMethod = true; + } + } else if (deferredMethod) { + deferredMethodSignature = "void methodname()"; + } + + if (deferredMethod && deferredValue) { + err.jspError(n, "jsp.error.deferredmethodandvalue"); + } + + 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"; + + if (deferredValue) { + type = ValueExpression.class.getName(); + } else if (deferredMethod) { + type = MethodExpression.class.getName(); + } + } + + if (("2.0".equals(tagLibInfo.getRequiredVersion()) || ("1.2".equals(tagLibInfo.getRequiredVersion()))) + && (deferredMethodSpecified || deferredMethod + || deferredValueSpecified || deferredValue)) { + err.jspError("jsp.error.invalid.version", path); + } + + TagAttributeInfo tagAttributeInfo = new TagAttributeInfo(attrName, + required, type, rtexprvalue, fragment, null, deferredValue, + deferredMethod, deferredValueType, deferredMethodSignature); + 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, tagLibInfo.getReliableURN(), 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. + * @deprecated Use {@link TagFileProcessor#parseTagFileDirectives( + * ParserController, String, String, URL, TagLibraryInfo)} + * See https://issues.apache.org/bugzilla/show_bug.cgi?id=46471 + */ + public static TagInfo parseTagFileDirectives(ParserController pc, + String name, String path, TagLibraryInfo tagLibInfo) + throws JasperException { + return parseTagFileDirectives(pc, name, path, + pc.getJspCompilationContext().getTagFileJarUrl(path), + tagLibInfo); + } + + /** + * 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 tagFileJarUrl + * the url for the Jar containign the tag file + * @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, URL tagFileJarUrl, TagLibraryInfo tagLibInfo) + throws JasperException { + + ErrorDispatcher err = pc.getCompiler().getErrorDispatcher(); + + Node.Nodes page = null; + try { + page = pc.parseTagFileDirectives(path, tagFileJarUrl); + } 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 { + + URL tagFileJarUrl = null; + if (tagFilePath.startsWith("/META-INF/")) { + try { + tagFileJarUrl = new URL("jar:" + + compiler.getCompilationContext().getTldLocation( + tagInfo.getTagLibrary().getURI())[0] + "!/"); + } catch (MalformedURLException e) { + // Ignore - tagFileJarUrl will be null + } + } + String tagFileJarPath; + if (tagFileJarUrl == null) { + tagFileJarPath = ""; + } else { + tagFileJarPath = tagFileJarUrl.toString(); + } + + JspCompilationContext ctxt = compiler.getCompilationContext(); + JspRuntimeContext rctxt = ctxt.getRuntimeContext(); + JspServletWrapper wrapper = rctxt.getWrapper(tagFileJarPath + tagFilePath); + + synchronized (rctxt) { + if (wrapper == null) { + wrapper = new JspServletWrapper(ctxt.getServletContext(), ctxt + .getOptions(), tagFilePath, tagInfo, ctxt + .getRuntimeContext(), tagFileJarUrl); + rctxt.addWrapper(tagFileJarPath + 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(), + ctxt.getTagFileJarUrl(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(); + if (tagFilePath.startsWith("/META-INF/")) { + // For tags in JARs, add the TLD and the tag as a dependency + String[] location = + compiler.getCompilationContext().getTldLocation( + tagFileInfo.getTagInfo().getTagLibrary().getURI()); + // Add TLD + pageInfo.addDependant("jar:" + location[0] + "!/" + + location[1]); + // Add Tag + pageInfo.addDependant("jar:" + location[0] + "!" + + tagFilePath); + } else { + 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; + } + } + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagLibraryInfoImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagLibraryInfoImpl.java new file mode 100644 index 000000000..7c0291d7d --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagLibraryInfoImpl.java @@ -0,0 +1,768 @@ +/* + * 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.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.JarURLConnection; +import java.net.URL; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.Vector; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +import javax.servlet.jsp.tagext.FunctionInfo; +import javax.servlet.jsp.tagext.PageData; +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.TagLibraryValidator; +import javax.servlet.jsp.tagext.TagVariableInfo; +import javax.servlet.jsp.tagext.ValidationMessage; +import javax.servlet.jsp.tagext.VariableInfo; + +import org.apache.jasper.JasperException; +import org.apache.jasper.JspCompilationContext; +import org.apache.jasper.xmlparser.ParserUtils; +import org.apache.jasper.xmlparser.TreeNode; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +/** + * Implementation of the TagLibraryInfo class from the JSP spec. + * + * @author Anil K. Vijendran + * @author Mandar Raje + * @author Pierre Delisle + * @author Kin-man Chung + * @author Jan Luehe + */ +class TagLibraryInfoImpl extends TagLibraryInfo implements TagConstants { + + // Logger + private Log log = LogFactory.getLog(TagLibraryInfoImpl.class); + + private JspCompilationContext ctxt; + + private PageInfo pi; + + private ErrorDispatcher err; + + private ParserController parserController; + + private final void print(String name, String value, PrintWriter w) { + if (value != null) { + w.print(name + " = {\n\t"); + w.print(value); + w.print("\n}\n"); + } + } + + public String toString() { + StringWriter sw = new StringWriter(); + PrintWriter out = new PrintWriter(sw); + print("tlibversion", tlibversion, out); + print("jspversion", jspversion, out); + print("shortname", shortname, out); + print("urn", urn, out); + print("info", info, out); + print("uri", uri, out); + print("tagLibraryValidator", "" + tagLibraryValidator, out); + + for (int i = 0; i < tags.length; i++) + out.println(tags[i].toString()); + + for (int i = 0; i < tagFiles.length; i++) + out.println(tagFiles[i].toString()); + + for (int i = 0; i < functions.length; i++) + out.println(functions[i].toString()); + + return sw.toString(); + } + + // XXX FIXME + // resolveRelativeUri and/or getResourceAsStream don't seem to properly + // handle relative paths when dealing when home and getDocBase are set + // the following is a workaround until these problems are resolved. + private InputStream getResourceAsStream(String uri) + throws FileNotFoundException { + try { + // see if file exists on the filesystem first + String real = ctxt.getRealPath(uri); + if (real == null) { + return ctxt.getResourceAsStream(uri); + } else { + return new FileInputStream(real); + } + } catch (FileNotFoundException ex) { + // if file not found on filesystem, get the resource through + // the context + return ctxt.getResourceAsStream(uri); + } + + } + + /** + * Constructor. + */ + public TagLibraryInfoImpl(JspCompilationContext ctxt, ParserController pc, PageInfo pi, + String prefix, String uriIn, String[] location, ErrorDispatcher err) + throws JasperException { + super(prefix, uriIn); + + this.ctxt = ctxt; + this.parserController = pc; + this.pi = pi; + this.err = err; + InputStream in = null; + JarFile jarFile = null; + + if (location == null) { + // The URI points to the TLD itself or to a JAR file in which the + // TLD is stored + location = generateTLDLocation(uri, ctxt); + } + + try { + if (!location[0].endsWith("jar")) { + // Location points to TLD file + try { + in = getResourceAsStream(location[0]); + if (in == null) { + throw new FileNotFoundException(location[0]); + } + } catch (FileNotFoundException ex) { + err.jspError("jsp.error.file.not.found", location[0]); + } + + parseTLD(ctxt, location[0], in, null); + // Add TLD to dependency list + PageInfo pageInfo = ctxt.createCompiler().getPageInfo(); + if (pageInfo != null) { + pageInfo.addDependant(location[0]); + } + } else { + // Tag library is packaged in JAR file + try { + URL jarFileUrl = new URL("jar:" + location[0] + "!/"); + JarURLConnection conn = (JarURLConnection) jarFileUrl + .openConnection(); + conn.setUseCaches(false); + conn.connect(); + jarFile = conn.getJarFile(); + ZipEntry jarEntry = jarFile.getEntry(location[1]); + in = jarFile.getInputStream(jarEntry); + parseTLD(ctxt, location[0], in, jarFileUrl); + } catch (Exception ex) { + err.jspError("jsp.error.tld.unable_to_read", location[0], + location[1], ex.toString()); + } + } + } finally { + if (in != null) { + try { + in.close(); + } catch (Throwable t) { + } + } + if (jarFile != null) { + try { + jarFile.close(); + } catch (Throwable t) { + } + } + } + + } + + public TagLibraryInfo[] getTagLibraryInfos() { + Collection coll = pi.getTaglibs(); + return (TagLibraryInfo[]) coll.toArray(new TagLibraryInfo[0]); + } + + /* + * @param ctxt The JSP compilation context @param uri The TLD's uri @param + * in The TLD's input stream @param jarFileUrl The JAR file containing the + * TLD, or null if the tag library is not packaged in a JAR + */ + private void parseTLD(JspCompilationContext ctxt, String uri, + InputStream in, URL jarFileUrl) throws JasperException { + Vector tagVector = new Vector(); + Vector tagFileVector = new Vector(); + Hashtable functionTable = new Hashtable(); + + // Create an iterator over the child elements of our element + ParserUtils pu = new ParserUtils(); + TreeNode tld = pu.parseXMLDocument(uri, in); + + // Check to see if the root element contains a 'version' + // attribute, which was added in JSP 2.0 to replace the + // subelement + this.jspversion = tld.findAttribute("version"); + + // Process each child element of our element + Iterator list = tld.findChildren(); + + while (list.hasNext()) { + TreeNode element = (TreeNode) list.next(); + String tname = element.getName(); + + if ("tlibversion".equals(tname) // JSP 1.1 + || "tlib-version".equals(tname)) { // JSP 1.2 + this.tlibversion = element.getBody(); + } else if ("jspversion".equals(tname) + || "jsp-version".equals(tname)) { + this.jspversion = element.getBody(); + } else if ("shortname".equals(tname) || "short-name".equals(tname)) + this.shortname = element.getBody(); + else if ("uri".equals(tname)) + this.urn = element.getBody(); + else if ("info".equals(tname) || "description".equals(tname)) + this.info = element.getBody(); + else if ("validator".equals(tname)) + this.tagLibraryValidator = createValidator(element); + else if ("tag".equals(tname)) + tagVector.addElement(createTagInfo(element, jspversion)); + else if ("tag-file".equals(tname)) { + TagFileInfo tagFileInfo = createTagFileInfo(element, uri, + jarFileUrl); + tagFileVector.addElement(tagFileInfo); + } else if ("function".equals(tname)) { // JSP2.0 + FunctionInfo funcInfo = createFunctionInfo(element); + String funcName = funcInfo.getName(); + if (functionTable.containsKey(funcName)) { + err.jspError("jsp.error.tld.fn.duplicate.name", funcName, + uri); + + } + functionTable.put(funcName, funcInfo); + } else if ("display-name".equals(tname) || // Ignored elements + "small-icon".equals(tname) || "large-icon".equals(tname) + || "listener".equals(tname)) { + ; + } else if ("taglib-extension".equals(tname)) { + // Recognized but ignored + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage( + "jsp.warning.unknown.element.in.taglib", tname)); + } + } + + } + + if (tlibversion == null) { + err.jspError("jsp.error.tld.mandatory.element.missing", + "tlib-version"); + } + if (jspversion == null) { + err.jspError("jsp.error.tld.mandatory.element.missing", + "jsp-version"); + } + + this.tags = new TagInfo[tagVector.size()]; + tagVector.copyInto(this.tags); + + this.tagFiles = new TagFileInfo[tagFileVector.size()]; + tagFileVector.copyInto(this.tagFiles); + + this.functions = new FunctionInfo[functionTable.size()]; + int i = 0; + Enumeration enumeration = functionTable.elements(); + while (enumeration.hasMoreElements()) { + this.functions[i++] = (FunctionInfo) enumeration.nextElement(); + } + } + + /* + * @param uri The uri of the TLD @param ctxt The compilation context + * + * @return String array whose first element denotes the path to the TLD. If + * the path to the TLD points to a jar file, then the second element denotes + * the name of the TLD entry in the jar file, which is hardcoded to + * META-INF/taglib.tld. + */ + private String[] generateTLDLocation(String uri, JspCompilationContext ctxt) + throws JasperException { + + int uriType = TldLocationsCache.uriType(uri); + if (uriType == TldLocationsCache.ABS_URI) { + err.jspError("jsp.error.taglibDirective.absUriCannotBeResolved", + uri); + } else if (uriType == TldLocationsCache.NOROOT_REL_URI) { + uri = ctxt.resolveRelativeUri(uri); + } + + String[] location = new String[2]; + location[0] = uri; + if (location[0].endsWith("jar")) { + URL url = null; + try { + url = ctxt.getResource(location[0]); + } catch (Exception ex) { + err.jspError("jsp.error.tld.unable_to_get_jar", location[0], ex + .toString()); + } + if (url == null) { + err.jspError("jsp.error.tld.missing_jar", location[0]); + } + location[0] = url.toString(); + location[1] = "META-INF/taglib.tld"; + } + + return location; + } + + private TagInfo createTagInfo(TreeNode elem, String jspVersion) + throws JasperException { + + String tagName = null; + String tagClassName = null; + String teiClassName = null; + + /* + * Default body content for JSP 1.2 tag handlers ( has + * become mandatory in JSP 2.0, because the default would be invalid for + * simple tag handlers) + */ + String bodycontent = "JSP"; + + String info = null; + String displayName = null; + String smallIcon = null; + String largeIcon = null; + boolean dynamicAttributes = false; + + Vector attributeVector = new Vector(); + Vector variableVector = new Vector(); + Iterator list = elem.findChildren(); + while (list.hasNext()) { + TreeNode element = (TreeNode) list.next(); + String tname = element.getName(); + + if ("name".equals(tname)) { + tagName = element.getBody(); + } else if ("tagclass".equals(tname) || "tag-class".equals(tname)) { + tagClassName = element.getBody(); + } else if ("teiclass".equals(tname) || "tei-class".equals(tname)) { + teiClassName = element.getBody(); + } else if ("bodycontent".equals(tname) + || "body-content".equals(tname)) { + bodycontent = element.getBody(); + } else if ("display-name".equals(tname)) { + displayName = element.getBody(); + } else if ("small-icon".equals(tname)) { + smallIcon = element.getBody(); + } else if ("large-icon".equals(tname)) { + largeIcon = element.getBody(); + } else if ("icon".equals(tname)) { + TreeNode icon = element.findChild("small-icon"); + if (icon != null) { + smallIcon = icon.getBody(); + } + icon = element.findChild("large-icon"); + if (icon != null) { + largeIcon = icon.getBody(); + } + } else if ("info".equals(tname) || "description".equals(tname)) { + info = element.getBody(); + } else if ("variable".equals(tname)) { + variableVector.addElement(createVariable(element)); + } else if ("attribute".equals(tname)) { + attributeVector + .addElement(createAttribute(element, jspVersion)); + } else if ("dynamic-attributes".equals(tname)) { + dynamicAttributes = JspUtil.booleanValue(element.getBody()); + } else if ("example".equals(tname)) { + // Ignored elements + } else if ("tag-extension".equals(tname)) { + // Ignored + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage( + "jsp.warning.unknown.element.in.tag", tname)); + } + } + } + + TagExtraInfo tei = null; + if (teiClassName != null && !teiClassName.equals("")) { + try { + Class teiClass = ctxt.getClassLoader().loadClass(teiClassName); + tei = (TagExtraInfo) teiClass.newInstance(); + } catch (Exception e) { + err.jspError("jsp.error.teiclass.instantiation", teiClassName, + e); + } + } + + TagAttributeInfo[] tagAttributeInfo = new TagAttributeInfo[attributeVector + .size()]; + attributeVector.copyInto(tagAttributeInfo); + + TagVariableInfo[] tagVariableInfos = new TagVariableInfo[variableVector + .size()]; + variableVector.copyInto(tagVariableInfos); + + TagInfo taginfo = new TagInfo(tagName, tagClassName, bodycontent, info, + this, tei, tagAttributeInfo, displayName, smallIcon, largeIcon, + tagVariableInfos, dynamicAttributes); + return taginfo; + } + + /* + * Parses the tag file directives of the given TagFile and turns them into a + * TagInfo. + * + * @param elem The element in the TLD @param uri The location of + * the TLD, in case the tag file is specified relative to it @param jarFile + * The JAR file, in case the tag file is packaged in a JAR + * + * @return TagInfo correspoding to tag file directives + */ + private TagFileInfo createTagFileInfo(TreeNode elem, String uri, + URL jarFileUrl) throws JasperException { + + String name = null; + String path = null; + + Iterator list = elem.findChildren(); + while (list.hasNext()) { + TreeNode child = (TreeNode) list.next(); + String tname = child.getName(); + if ("name".equals(tname)) { + name = child.getBody(); + } else if ("path".equals(tname)) { + path = child.getBody(); + } else if ("example".equals(tname)) { + // Ignore element: Bugzilla 33538 + } else if ("tag-extension".equals(tname)) { + // Ignore element: Bugzilla 33538 + } else if ("icon".equals(tname) + || "display-name".equals(tname) + || "description".equals(tname)) { + // Ignore these elements: Bugzilla 38015 + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage( + "jsp.warning.unknown.element.in.tagfile", tname)); + } + } + } + + if (path.startsWith("/META-INF/tags")) { + // Tag file packaged in JAR + // See https://issues.apache.org/bugzilla/show_bug.cgi?id=46471 + // This needs to be removed once all the broken code that depends on + // it has been removed + ctxt.setTagFileJarUrl(path, jarFileUrl); + } else if (!path.startsWith("/WEB-INF/tags")) { + err.jspError("jsp.error.tagfile.illegalPath", path); + } + + TagInfo tagInfo = TagFileProcessor.parseTagFileDirectives( + parserController, name, path, jarFileUrl, this); + return new TagFileInfo(name, path, tagInfo); + } + + TagAttributeInfo createAttribute(TreeNode elem, String jspVersion) { + String name = null; + String type = null; + String expectedType = null; + String methodSignature = null; + boolean required = false, rtexprvalue = false, reqTime = false, isFragment = false, deferredValue = false, deferredMethod = false; + + Iterator list = elem.findChildren(); + while (list.hasNext()) { + TreeNode element = (TreeNode) list.next(); + String tname = element.getName(); + + if ("name".equals(tname)) { + name = element.getBody(); + } else if ("required".equals(tname)) { + String s = element.getBody(); + if (s != null) + required = JspUtil.booleanValue(s); + } else if ("rtexprvalue".equals(tname)) { + String s = element.getBody(); + if (s != null) + rtexprvalue = JspUtil.booleanValue(s); + } else if ("type".equals(tname)) { + type = element.getBody(); + if ("1.2".equals(jspVersion) + && (type.equals("Boolean") || type.equals("Byte") + || type.equals("Character") + || type.equals("Double") + || type.equals("Float") + || type.equals("Integer") + || type.equals("Long") || type.equals("Object") + || type.equals("Short") || type + .equals("String"))) { + type = "java.lang." + type; + } + } else if ("fragment".equals(tname)) { + String s = element.getBody(); + if (s != null) { + isFragment = JspUtil.booleanValue(s); + } + } else if ("deferred-value".equals(tname)) { + deferredValue = true; + type = "javax.el.ValueExpression"; + TreeNode child = element.findChild("type"); + if (child != null) { + expectedType = child.getBody(); + if (expectedType != null) { + expectedType = expectedType.trim(); + } + } else { + expectedType = "java.lang.Object"; + } + } else if ("deferred-method".equals(tname)) { + deferredMethod = true; + type = "javax.el.MethodExpression"; + TreeNode child = element.findChild("method-signature"); + if (child != null) { + methodSignature = child.getBody(); + if (methodSignature != null) { + methodSignature = methodSignature.trim(); + } + } else { + methodSignature = "java.lang.Object method()"; + } + } else if ("description".equals(tname) || // Ignored elements + false) { + ; + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage( + "jsp.warning.unknown.element.in.attribute", tname)); + } + } + } + + if (isFragment) { + /* + * According to JSP.C-3 ("TLD Schema Element Structure - tag"), + * 'type' and 'rtexprvalue' must not be specified if 'fragment' has + * been specified (this will be enforced by validating parser). + * Also, if 'fragment' is TRUE, 'type' is fixed at + * javax.servlet.jsp.tagext.JspFragment, and 'rtexprvalue' is fixed + * at true. See also JSP.8.5.2. + */ + type = "javax.servlet.jsp.tagext.JspFragment"; + rtexprvalue = true; + } + + if (!rtexprvalue && type == null) { + // According to JSP spec, for static values (those determined at + // translation time) the type is fixed at java.lang.String. + type = "java.lang.String"; + } + + return new TagAttributeInfo(name, required, type, rtexprvalue, + isFragment, null, deferredValue, deferredMethod, expectedType, + methodSignature); + } + + TagVariableInfo createVariable(TreeNode elem) { + String nameGiven = null; + String nameFromAttribute = null; + String className = "java.lang.String"; + boolean declare = true; + int scope = VariableInfo.NESTED; + + Iterator list = elem.findChildren(); + while (list.hasNext()) { + TreeNode element = (TreeNode) list.next(); + String tname = element.getName(); + if ("name-given".equals(tname)) + nameGiven = element.getBody(); + else if ("name-from-attribute".equals(tname)) + nameFromAttribute = element.getBody(); + else if ("variable-class".equals(tname)) + className = element.getBody(); + else if ("declare".equals(tname)) { + String s = element.getBody(); + if (s != null) + declare = JspUtil.booleanValue(s); + } else if ("scope".equals(tname)) { + String s = element.getBody(); + if (s != null) { + if ("NESTED".equals(s)) { + scope = VariableInfo.NESTED; + } else if ("AT_BEGIN".equals(s)) { + scope = VariableInfo.AT_BEGIN; + } else if ("AT_END".equals(s)) { + scope = VariableInfo.AT_END; + } + } + } else if ("description".equals(tname) || // Ignored elements + false) { + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage( + "jsp.warning.unknown.element.in.variable", tname)); + } + } + } + return new TagVariableInfo(nameGiven, nameFromAttribute, className, + declare, scope); + } + + private TagLibraryValidator createValidator(TreeNode elem) + throws JasperException { + + String validatorClass = null; + Map initParams = new Hashtable(); + + Iterator list = elem.findChildren(); + while (list.hasNext()) { + TreeNode element = (TreeNode) list.next(); + String tname = element.getName(); + if ("validator-class".equals(tname)) + validatorClass = element.getBody(); + else if ("init-param".equals(tname)) { + String[] initParam = createInitParam(element); + initParams.put(initParam[0], initParam[1]); + } else if ("description".equals(tname) || // Ignored elements + false) { + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage( + "jsp.warning.unknown.element.in.validator", tname)); + } + } + } + + TagLibraryValidator tlv = null; + if (validatorClass != null && !validatorClass.equals("")) { + try { + Class tlvClass = ctxt.getClassLoader() + .loadClass(validatorClass); + tlv = (TagLibraryValidator) tlvClass.newInstance(); + } catch (Exception e) { + err.jspError("jsp.error.tlvclass.instantiation", + validatorClass, e); + } + } + if (tlv != null) { + tlv.setInitParameters(initParams); + } + return tlv; + } + + String[] createInitParam(TreeNode elem) { + String[] initParam = new String[2]; + + Iterator list = elem.findChildren(); + while (list.hasNext()) { + TreeNode element = (TreeNode) list.next(); + String tname = element.getName(); + if ("param-name".equals(tname)) { + initParam[0] = element.getBody(); + } else if ("param-value".equals(tname)) { + initParam[1] = element.getBody(); + } else if ("description".equals(tname)) { + // Do nothing + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage( + "jsp.warning.unknown.element.in.initParam", tname)); + } + } + } + return initParam; + } + + FunctionInfo createFunctionInfo(TreeNode elem) { + + String name = null; + String klass = null; + String signature = null; + + Iterator list = elem.findChildren(); + while (list.hasNext()) { + TreeNode element = (TreeNode) list.next(); + String tname = element.getName(); + + if ("name".equals(tname)) { + name = element.getBody(); + } else if ("function-class".equals(tname)) { + klass = element.getBody(); + } else if ("function-signature".equals(tname)) { + signature = element.getBody(); + } else if ("display-name".equals(tname) || // Ignored elements + "small-icon".equals(tname) || "large-icon".equals(tname) + || "description".equals(tname) || "example".equals(tname)) { + } else { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage( + "jsp.warning.unknown.element.in.function", tname)); + } + } + } + + return new FunctionInfo(name, klass, signature); + } + + // ********************************************************************* + // Until javax.servlet.jsp.tagext.TagLibraryInfo is fixed + + /** + * The instance (if any) for the TagLibraryValidator class. + * + * @return The TagLibraryValidator instance, if any. + */ + public TagLibraryValidator getTagLibraryValidator() { + return tagLibraryValidator; + } + + /** + * Translation-time validation of the XML document associated with the JSP + * page. This is a convenience method on the associated TagLibraryValidator + * class. + * + * @param thePage + * The JSP page object + * @return A string indicating whether the page is valid or not. + */ + public ValidationMessage[] validate(PageData thePage) { + TagLibraryValidator tlv = getTagLibraryValidator(); + if (tlv == null) + return null; + + String uri = getURI(); + if (uri.startsWith("/")) { + uri = URN_JSPTLD + uri; + } + + return tlv.validate(getPrefixString(), uri, thePage); + } + + protected TagLibraryValidator tagLibraryValidator; +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagPluginManager.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagPluginManager.java new file mode 100644 index 000000000..e95d0382c --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagPluginManager.java @@ -0,0 +1,240 @@ +/* + * 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.util.*; +import java.io.*; +import javax.servlet.ServletContext; + +import org.apache.jasper.JasperException; +import org.apache.jasper.xmlparser.ParserUtils; +import org.apache.jasper.xmlparser.TreeNode; +import org.apache.jasper.compiler.tagplugin.TagPlugin; +import org.apache.jasper.compiler.tagplugin.TagPluginContext; + +/** + * Manages tag plugin optimizations. + * @author Kin-man Chung + */ + +public class TagPluginManager { + + private static final String TAG_PLUGINS_XML = "/WEB-INF/tagPlugins.xml"; + private static final String TAG_PLUGINS_ROOT_ELEM = "tag-plugins"; + + private boolean initialized = false; + private HashMap tagPlugins = null; + private ServletContext ctxt; + private PageInfo pageInfo; + + public TagPluginManager(ServletContext ctxt) { + this.ctxt = ctxt; + } + + public void apply(Node.Nodes page, ErrorDispatcher err, PageInfo pageInfo) + throws JasperException { + + init(err); + if (tagPlugins == null || tagPlugins.size() == 0) { + return; + } + + this.pageInfo = pageInfo; + + page.visit(new Node.Visitor() { + public void visit(Node.CustomTag n) + throws JasperException { + invokePlugin(n); + visitBody(n); + } + }); + + } + + private void init(ErrorDispatcher err) throws JasperException { + if (initialized) + return; + + InputStream is = ctxt.getResourceAsStream(TAG_PLUGINS_XML); + if (is == null) + return; + + TreeNode root = (new ParserUtils()).parseXMLDocument(TAG_PLUGINS_XML, + is); + if (root == null) { + return; + } + + if (!TAG_PLUGINS_ROOT_ELEM.equals(root.getName())) { + err.jspError("jsp.error.plugin.wrongRootElement", TAG_PLUGINS_XML, + TAG_PLUGINS_ROOT_ELEM); + } + + tagPlugins = new HashMap(); + Iterator pluginList = root.findChildren("tag-plugin"); + while (pluginList.hasNext()) { + TreeNode pluginNode = (TreeNode) pluginList.next(); + TreeNode tagClassNode = pluginNode.findChild("tag-class"); + if (tagClassNode == null) { + // Error + return; + } + String tagClass = tagClassNode.getBody().trim(); + TreeNode pluginClassNode = pluginNode.findChild("plugin-class"); + if (pluginClassNode == null) { + // Error + return; + } + + String pluginClassStr = pluginClassNode.getBody(); + TagPlugin tagPlugin = null; + try { + Class pluginClass = Class.forName(pluginClassStr); + tagPlugin = (TagPlugin) pluginClass.newInstance(); + } catch (Exception e) { + throw new JasperException(e); + } + if (tagPlugin == null) { + return; + } + tagPlugins.put(tagClass, tagPlugin); + } + initialized = true; + } + + /** + * Invoke tag plugin for the given custom tag, if a plugin exists for + * the custom tag's tag handler. + * + * The given custom tag node will be manipulated by the plugin. + */ + private void invokePlugin(Node.CustomTag n) { + TagPlugin tagPlugin = (TagPlugin) + tagPlugins.get(n.getTagHandlerClass().getName()); + if (tagPlugin == null) { + return; + } + + TagPluginContext tagPluginContext = new TagPluginContextImpl(n, pageInfo); + n.setTagPluginContext(tagPluginContext); + tagPlugin.doTag(tagPluginContext); + } + + static class TagPluginContextImpl implements TagPluginContext { + private Node.CustomTag node; + private Node.Nodes curNodes; + private PageInfo pageInfo; + private HashMap pluginAttributes; + + 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(); + } + + public TagPluginContext getParentContext() { + Node parent = node.getParent(); + if (! (parent instanceof Node.CustomTag)) { + return null; + } + return ((Node.CustomTag) parent).getTagPluginContext(); + } + + public void setPluginAttribute(String key, Object value) { + pluginAttributes.put(key, value); + } + + public Object getPluginAttribute(String key) { + return pluginAttributes.get(key); + } + + public boolean isScriptless() { + return node.getChildInfo().isScriptless(); + } + + public boolean isConstantAttribute(String attribute) { + Node.JspAttribute attr = getNodeAttribute(attribute); + if (attr == null) + return false; + return attr.isLiteral(); + } + + public String getConstantAttribute(String attribute) { + Node.JspAttribute attr = getNodeAttribute(attribute); + if (attr == null) + return null; + return attr.getValue(); + } + + public boolean isAttributeSpecified(String attribute) { + return getNodeAttribute(attribute) != null; + } + + public String getTemporaryVariableName() { + return node.getRoot().nextTemporaryVariableName(); + } + + public void generateImport(String imp) { + pageInfo.addImport(imp); + } + + public void generateDeclaration(String id, String text) { + if (pageInfo.isPluginDeclared(id)) { + return; + } + curNodes.add(new Node.Declaration(text, node.getStart(), null)); + } + + public void generateJavaSource(String sourceCode) { + curNodes.add(new Node.Scriptlet(sourceCode, node.getStart(), + null)); + } + + public void generateAttribute(String attributeName) { + curNodes.add(new Node.AttributeGenerator(node.getStart(), + attributeName, + node)); + } + + public void dontUseTagPlugin() { + node.setUseTagPlugin(false); + } + + 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(); + } + + 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; + } + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TextOptimizer.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TextOptimizer.java new file mode 100644 index 000000000..82df5216f --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TextOptimizer.java @@ -0,0 +1,115 @@ +/* + * 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 org.apache.jasper.JasperException; +import org.apache.jasper.Options; + +/** + */ +public class TextOptimizer { + + /** + * A visitor to concatenate contiguous template texts. + */ + static class TextCatVisitor extends Node.Visitor { + + private Options options; + private PageInfo pageInfo; + private int textNodeCount = 0; + private Node.TemplateText firstTextNode = null; + private StringBuffer textBuffer; + private final String emptyText = new String(""); + + public TextCatVisitor(Compiler compiler) { + options = compiler.getCompilationContext().getOptions(); + pageInfo = compiler.getPageInfo(); + } + + public void doVisit(Node n) throws JasperException { + collectText(); + } + + /* + * The following directis are ignored in text concatenation + */ + + public void visit(Node.PageDirective n) throws JasperException { + } + + public void visit(Node.TagDirective n) throws JasperException { + } + + public void visit(Node.TaglibDirective n) throws JasperException { + } + + public void visit(Node.AttributeDirective n) throws JasperException { + } + + public void visit(Node.VariableDirective n) throws JasperException { + } + + /* + * Don't concatenate text across body boundaries + */ + public void visitBody(Node n) throws JasperException { + super.visitBody(n); + collectText(); + } + + public void visit(Node.TemplateText n) throws JasperException { + if ((options.getTrimSpaces() || pageInfo.isTrimDirectiveWhitespaces()) + && n.isAllSpace()) { + n.setText(emptyText); + return; + } + + if (textNodeCount++ == 0) { + firstTextNode = n; + textBuffer = new StringBuffer(n.getText()); + } else { + // Append text to text buffer + textBuffer.append(n.getText()); + n.setText(emptyText); + } + } + + /** + * This method breaks concatenation mode. As a side effect it copies + * the concatenated string to the first text node + */ + private void collectText() { + + if (textNodeCount > 1) { + // Copy the text in buffer into the first template text node. + firstTextNode.setText(textBuffer.toString()); + } + textNodeCount = 0; + } + + } + + public static void concatenate(Compiler compiler, Node.Nodes page) + throws JasperException { + + TextCatVisitor v = new TextCatVisitor(compiler); + page.visit(v); + + // Cleanup, in case the page ends with a template text + v.collectText(); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TldLocationsCache.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TldLocationsCache.java new file mode 100644 index 000000000..023bb0c5b --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TldLocationsCache.java @@ -0,0 +1,549 @@ +/* + * 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.InputStream; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import org.xml.sax.InputSource; + +import javax.servlet.ServletContext; + +import org.apache.jasper.Constants; +import org.apache.jasper.JasperException; +import org.apache.jasper.xmlparser.ParserUtils; +import org.apache.jasper.xmlparser.TreeNode; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +/** + * A container for all tag libraries that are defined "globally" + * for the web application. + * + * Tag Libraries can be defined globally in one of two ways: + * 1. Via elements in web.xml: + * the uri and location of the tag-library are specified in + * the element. + * 2. Via packaged jar files that contain .tld files + * within the META-INF directory, or some subdirectory + * of it. The taglib is 'global' if it has the + * element defined. + * + * A mapping between the taglib URI and its associated TaglibraryInfoImpl + * is maintained in this container. + * Actually, that's what we'd like to do. However, because of the + * way the classes TagLibraryInfo and TagInfo have been defined, + * it is not currently possible to share an instance of TagLibraryInfo + * across page invocations. A bug has been submitted to the spec lead. + * In the mean time, all we do is save the 'location' where the + * TLD associated with a taglib URI can be found. + * + * When a JSP page has a taglib directive, the mappings in this container + * are first searched (see method getLocation()). + * If a mapping is found, then the location of the TLD is returned. + * If no mapping is found, then the uri specified + * in the taglib directive is to be interpreted as the location for + * the TLD of this tag library. + * + * @author Pierre Delisle + * @author Jan Luehe + */ + +public class TldLocationsCache { + + // Logger + private Log log = LogFactory.getLog(TldLocationsCache.class); + + /** + * The types of URI one may specify for a tag library + */ + public static final int ABS_URI = 0; + public static final int ROOT_REL_URI = 1; + public static final int NOROOT_REL_URI = 2; + + private static final String WEB_XML = "/WEB-INF/web.xml"; + private static final String FILE_PROTOCOL = "file:"; + private static final String JAR_FILE_SUFFIX = ".jar"; + + // Names of JARs that are known not to contain any TLDs + private static HashSet noTldJars; + + /** + * The mapping of the 'global' tag library URI to the location (resource + * path) of the TLD associated with that tag library. The location is + * returned as a String array: + * [0] The location + * [1] If the location is a jar file, this is the location of the tld. + */ + private Hashtable mappings; + + private boolean initialized; + private ServletContext ctxt; + private boolean redeployMode; + + //********************************************************************* + // Constructor and Initilizations + + /* + * Initializes the set of JARs that are known not to contain any TLDs + */ + static { + noTldJars = new HashSet(); + // Bootstrap JARs + noTldJars.add("bootstrap.jar"); + noTldJars.add("commons-daemon.jar"); + noTldJars.add("tomcat-juli.jar"); + // Main JARs + noTldJars.add("annotations-api.jar"); + noTldJars.add("catalina.jar"); + noTldJars.add("catalina-ant.jar"); + noTldJars.add("catalina-ha.jar"); + noTldJars.add("catalina-tribes.jar"); + noTldJars.add("el-api.jar"); + noTldJars.add("jasper.jar"); + noTldJars.add("jasper-el.jar"); + noTldJars.add("jasper-jdt.jar"); + noTldJars.add("jsp-api.jar"); + noTldJars.add("servlet-api.jar"); + noTldJars.add("tomcat-coyote.jar"); + noTldJars.add("tomcat-dbcp.jar"); + // i18n JARs + noTldJars.add("tomcat-i18n-en.jar"); + noTldJars.add("tomcat-i18n-es.jar"); + noTldJars.add("tomcat-i18n-fr.jar"); + noTldJars.add("tomcat-i18n-ja.jar"); + // Misc JARs not included with Tomcat + noTldJars.add("ant.jar"); + noTldJars.add("commons-dbcp.jar"); + noTldJars.add("commons-beanutils.jar"); + noTldJars.add("commons-fileupload-1.0.jar"); + noTldJars.add("commons-pool.jar"); + noTldJars.add("commons-digester.jar"); + noTldJars.add("commons-logging.jar"); + noTldJars.add("commons-collections.jar"); + noTldJars.add("jmx.jar"); + noTldJars.add("jmx-tools.jar"); + noTldJars.add("xercesImpl.jar"); + noTldJars.add("xmlParserAPIs.jar"); + noTldJars.add("xml-apis.jar"); + // JARs from J2SE runtime + noTldJars.add("sunjce_provider.jar"); + noTldJars.add("ldapsec.jar"); + noTldJars.add("localedata.jar"); + noTldJars.add("dnsns.jar"); + noTldJars.add("tools.jar"); + noTldJars.add("sunpkcs11.jar"); + } + + public TldLocationsCache(ServletContext ctxt) { + this(ctxt, true); + } + + /** Constructor. + * + * @param ctxt the servlet context of the web application in which Jasper + * is running + * @param redeployMode if true, then the compiler will allow redeploying + * a tag library from the same jar, at the expense of slowing down the + * server a bit. Note that this may only work on JDK 1.3.1_01a and later, + * because of JDK bug 4211817 fixed in this release. + * If redeployMode is false, a faster but less capable mode will be used. + */ + public TldLocationsCache(ServletContext ctxt, boolean redeployMode) { + this.ctxt = ctxt; + this.redeployMode = redeployMode; + mappings = new Hashtable(); + initialized = false; + } + + /** + * Sets the list of JARs that are known not to contain any TLDs. + * + * @param jarNames List of comma-separated names of JAR files that are + * known not to contain any TLDs + */ + public static void setNoTldJars(String jarNames) { + if (jarNames != null) { + noTldJars.clear(); + StringTokenizer tokenizer = new StringTokenizer(jarNames, ","); + while (tokenizer.hasMoreElements()) { + noTldJars.add(tokenizer.nextToken()); + } + } + } + + /** + * Gets the 'location' of the TLD associated with the given taglib 'uri'. + * + * Returns null if the uri is not associated with any tag library 'exposed' + * in the web application. A tag library is 'exposed' either explicitly in + * web.xml or implicitly via the uri tag in the TLD of a taglib deployed + * in a jar file (WEB-INF/lib). + * + * @param uri The taglib uri + * + * @return An array of two Strings: The first element denotes the real + * path to the TLD. If the path to the TLD points to a jar file, then the + * second element denotes the name of the TLD entry in the jar file. + * Returns null if the uri is not associated with any tag library 'exposed' + * in the web application. + */ + public String[] getLocation(String uri) throws JasperException { + if (!initialized) { + init(); + } + return (String[]) mappings.get(uri); + } + + /** + * Returns the type of a URI: + * ABS_URI + * ROOT_REL_URI + * NOROOT_REL_URI + */ + public static int uriType(String uri) { + if (uri.indexOf(':') != -1) { + return ABS_URI; + } else if (uri.startsWith("/")) { + return ROOT_REL_URI; + } else { + return NOROOT_REL_URI; + } + } + + private void init() throws JasperException { + if (initialized) return; + try { + processWebDotXml(); + scanJars(); + processTldsInFileSystem("/WEB-INF/"); + initialized = true; + } catch (Exception ex) { + throw new JasperException(Localizer.getMessage( + "jsp.error.internal.tldinit", ex.getMessage())); + } + } + + /* + * Populates taglib map described in web.xml. + */ + private void processWebDotXml() throws Exception { + + InputStream is = null; + + try { + // Acquire input stream to web application deployment descriptor + String altDDName = (String)ctxt.getAttribute( + Constants.ALT_DD_ATTR); + URL uri = null; + if (altDDName != null) { + try { + uri = new URL(FILE_PROTOCOL+altDDName.replace('\\', '/')); + } catch (MalformedURLException e) { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage( + "jsp.error.internal.filenotfound", + altDDName)); + } + } + } else { + uri = ctxt.getResource(WEB_XML); + if (uri == null && log.isWarnEnabled()) { + log.warn(Localizer.getMessage( + "jsp.error.internal.filenotfound", + WEB_XML)); + } + } + + if (uri == null) { + return; + } + is = uri.openStream(); + InputSource ip = new InputSource(is); + ip.setSystemId(uri.toExternalForm()); + + // Parse the web application deployment descriptor + TreeNode webtld = null; + // altDDName is the absolute path of the DD + if (altDDName != null) { + webtld = new ParserUtils().parseXMLDocument(altDDName, ip); + } else { + webtld = new ParserUtils().parseXMLDocument(WEB_XML, ip); + } + + // Allow taglib to be an element of the root or jsp-config (JSP2.0) + TreeNode jspConfig = webtld.findChild("jsp-config"); + if (jspConfig != null) { + webtld = jspConfig; + } + Iterator taglibs = webtld.findChildren("taglib"); + while (taglibs.hasNext()) { + + // Parse the next element + TreeNode taglib = (TreeNode) taglibs.next(); + String tagUri = null; + String tagLoc = null; + TreeNode child = taglib.findChild("taglib-uri"); + if (child != null) + tagUri = child.getBody(); + child = taglib.findChild("taglib-location"); + if (child != null) + tagLoc = child.getBody(); + + // Save this location if appropriate + if (tagLoc == null) + continue; + if (uriType(tagLoc) == NOROOT_REL_URI) + tagLoc = "/WEB-INF/" + tagLoc; + String tagLoc2 = null; + if (tagLoc.endsWith(JAR_FILE_SUFFIX)) { + tagLoc = ctxt.getResource(tagLoc).toString(); + tagLoc2 = "META-INF/taglib.tld"; + } + mappings.put(tagUri, new String[] { tagLoc, tagLoc2 }); + } + } finally { + if (is != null) { + try { + is.close(); + } catch (Throwable t) {} + } + } + } + + /** + * Scans the given JarURLConnection for TLD files located in META-INF + * (or a subdirectory of it), adding an implicit map entry to the taglib + * map for any TLD that has a element. + * + * @param conn The JarURLConnection to the JAR file to scan + * @param ignore true if any exceptions raised when processing the given + * JAR should be ignored, false otherwise + */ + private void scanJar(JarURLConnection conn, boolean ignore) + throws JasperException { + + JarFile jarFile = null; + String resourcePath = conn.getJarFileURL().toString(); + try { + if (redeployMode) { + conn.setUseCaches(false); + } + jarFile = conn.getJarFile(); + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry) entries.nextElement(); + String name = entry.getName(); + if (!name.startsWith("META-INF/")) continue; + if (!name.endsWith(".tld")) continue; + InputStream stream = jarFile.getInputStream(entry); + try { + String uri = getUriFromTld(resourcePath, stream); + // Add implicit map entry only if its uri is not already + // present in the map + if (uri != null && mappings.get(uri) == null) { + mappings.put(uri, new String[]{ resourcePath, name }); + } + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Throwable t) { + // do nothing + } + } + } + } + } catch (Exception ex) { + if (!redeployMode) { + // if not in redeploy mode, close the jar in case of an error + if (jarFile != null) { + try { + jarFile.close(); + } catch (Throwable t) { + // ignore + } + } + } + if (!ignore) { + throw new JasperException(ex); + } + } finally { + if (redeployMode) { + // if in redeploy mode, always close the jar + if (jarFile != null) { + try { + jarFile.close(); + } catch (Throwable t) { + // ignore + } + } + } + } + } + + /* + * Searches the filesystem under /WEB-INF for any TLD files, and adds + * an implicit map entry to the taglib map for any TLD that has a + * element. + */ + private void processTldsInFileSystem(String startPath) + throws Exception { + + Set dirList = ctxt.getResourcePaths(startPath); + if (dirList != null) { + Iterator it = dirList.iterator(); + while (it.hasNext()) { + String path = (String) it.next(); + if (path.endsWith("/")) { + processTldsInFileSystem(path); + } + if (!path.endsWith(".tld")) { + continue; + } + InputStream stream = ctxt.getResourceAsStream(path); + String uri = null; + try { + uri = getUriFromTld(path, stream); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Throwable t) { + // do nothing + } + } + } + // Add implicit map entry only if its uri is not already + // present in the map + if (uri != null && mappings.get(uri) == null) { + mappings.put(uri, new String[] { path, null }); + } + } + } + } + + /* + * Returns the value of the uri element of the given TLD, or null if the + * given TLD does not contain any such element. + */ + private String getUriFromTld(String resourcePath, InputStream in) + throws JasperException + { + // Parse the tag library descriptor at the specified resource path + TreeNode tld = new ParserUtils().parseXMLDocument(resourcePath, in); + TreeNode uri = tld.findChild("uri"); + if (uri != null) { + String body = uri.getBody(); + if (body != null) + return body; + } + + return null; + } + + /* + * Scans all JARs accessible to the webapp's classloader and its + * parent classloaders for TLDs. + * + * The list of JARs always includes the JARs under WEB-INF/lib, as well as + * all shared JARs in the classloader delegation chain of the webapp's + * classloader. + * + * Considering JARs in the classloader delegation chain constitutes a + * Tomcat-specific extension to the TLD search + * order defined in the JSP spec. It allows tag libraries packaged as JAR + * files to be shared by web applications by simply dropping them in a + * location that all web applications have access to (e.g., + * /common/lib). + * + * The set of shared JARs to be scanned for TLDs is narrowed down by + * the noTldJars class variable, which contains the names of JARs + * that are known not to contain any TLDs. + */ + private void scanJars() throws Exception { + + ClassLoader webappLoader + = Thread.currentThread().getContextClassLoader(); + ClassLoader loader = webappLoader; + + while (loader != null) { + if (loader instanceof URLClassLoader) { + URL[] urls = ((URLClassLoader) loader).getURLs(); + for (int i=0; ijarPath needs to be + * scanned for TLDs. + * + * @param loader The current classloader in the parent chain + * @param webappLoader The webapp classloader + * @param jarPath The JAR file path + * + * @return TRUE if the JAR file identified by jarPath needs to be + * scanned for TLDs, FALSE otherwise + */ + private boolean needScanJar(ClassLoader loader, ClassLoader webappLoader, + String jarPath) { + if (loader == webappLoader) { + // JARs under WEB-INF/lib must be scanned unconditionally according + // to the spec. + return true; + } else { + String jarName = jarPath; + int slash = jarPath.lastIndexOf('/'); + if (slash >= 0) { + jarName = jarPath.substring(slash + 1); + } + return (!noTldJars.contains(jarName)); + } + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Validator.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Validator.java new file mode 100644 index 000000000..dd73aee73 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Validator.java @@ -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.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.el.lang.ELSupport; +import org.apache.jasper.Constants; +import org.apache.jasper.JasperException; +import org.apache.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 (). 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 tagDataAttrs = new Hashtable(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: + * + * , 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: + * + * + * + * 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 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 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 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. + *

+ * 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 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 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 params = new ArrayList(); + // Signature is of the form + // S ( ',' )* )? ')' + 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 fnmap = new HashMap(); + + 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("

"); + errMsg.append(Localizer.getMessage( + "jsp.error.tei.invalid.attributes", n.getQName())); + errMsg.append("

"); + for (int i = 0; i < errors.length; i++) { + errMsg.append("

"); + if (errors[i].getId() != null) { + errMsg.append(errors[i].getId()); + errMsg.append(": "); + } + errMsg.append(errors[i].getMessage()); + errMsg.append("

"); + } + + 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("

"); + errMsg.append(Localizer.getMessage( + "jsp.error.tlv.invalid.page", tli.getShortName(), + compiler.getPageInfo().getJspFile())); + errMsg.append("

"); + for (int i = 0; i < errors.length; i++) { + if (errors[i] != null) { + errMsg.append("

"); + errMsg.append(errors[i].getId()); + errMsg.append(": "); + errMsg.append(errors[i].getMessage()); + errMsg.append("

"); + } + } + } + } + + if (errMsg != null) { + errDisp.jspError(errMsg.toString()); + } + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPlugin.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPlugin.java new file mode 100644 index 000000000..49cbc7c0f --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPlugin.java @@ -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.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); +} + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPluginContext.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPluginContext.java new file mode 100644 index 000000000..8d1c558d1 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPluginContext.java @@ -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.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 for an example. + */ + void setPluginAttribute(String attr, Object value); + + /** + * Get the value of an attribute in the current tagplugin context. + */ + Object getPluginAttribute(String attr); +} + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ELContextImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ELContextImpl.java new file mode 100644 index 000000000..34e550c89 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ELContextImpl.java @@ -0,0 +1,99 @@ +/* + * 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.el; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import javax.el.ELContext; +import javax.el.ELResolver; +import javax.el.FunctionMapper; +import javax.el.ValueExpression; +import javax.el.VariableMapper; + +/** + * Implementation of ELContext + * + * @author Jacob Hookom + */ +public final class ELContextImpl extends ELContext { + + private final static FunctionMapper NullFunctionMapper = new FunctionMapper() { + public Method resolveFunction(String prefix, String localName) { + return null; + } + }; + + private final static class VariableMapperImpl extends VariableMapper { + + private Map vars; + + public ValueExpression resolveVariable(String variable) { + if (vars == null) { + return null; + } + return vars.get(variable); + } + + public ValueExpression setVariable(String variable, + ValueExpression expression) { + if (vars == null) + vars = new HashMap(); + return vars.put(variable, expression); + } + + } + + private final ELResolver resolver; + + private FunctionMapper functionMapper = NullFunctionMapper; // immutable + + private VariableMapper variableMapper; + + public ELContextImpl() { + this(ELResolverImpl.DefaultResolver); + } + + public ELContextImpl(ELResolver resolver) { + this.resolver = resolver; + } + + public ELResolver getELResolver() { + return this.resolver; + } + + public FunctionMapper getFunctionMapper() { + return this.functionMapper; + } + + public VariableMapper getVariableMapper() { + if (this.variableMapper == null) { + this.variableMapper = new VariableMapperImpl(); + } + return this.variableMapper; + } + + public void setFunctionMapper(FunctionMapper functionMapper) { + this.functionMapper = functionMapper; + } + + public void setVariableMapper(VariableMapper variableMapper) { + this.variableMapper = variableMapper; + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ELContextWrapper.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ELContextWrapper.java new file mode 100644 index 000000000..a0754c3ef --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ELContextWrapper.java @@ -0,0 +1,78 @@ +/* + * 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.el; + +import java.util.Locale; + +import javax.el.ELContext; +import javax.el.ELResolver; +import javax.el.FunctionMapper; +import javax.el.VariableMapper; + +/** + * Simple ELContextWrapper for runtime evaluation of EL w/ dynamic FunctionMappers + * + * @author jhook + */ +public final class ELContextWrapper extends ELContext { + + private final ELContext target; + private final FunctionMapper fnMapper; + + public ELContextWrapper(ELContext target, FunctionMapper fnMapper) { + this.target = target; + this.fnMapper = fnMapper; + } + + public ELResolver getELResolver() { + return this.target.getELResolver(); + } + + public FunctionMapper getFunctionMapper() { + if (this.fnMapper != null) return this.fnMapper; + return this.target.getFunctionMapper(); + } + + public VariableMapper getVariableMapper() { + return this.target.getVariableMapper(); + } + + public Object getContext(Class key) { + return this.target.getContext(key); + } + + public Locale getLocale() { + return this.target.getLocale(); + } + + public boolean isPropertyResolved() { + return this.target.isPropertyResolved(); + } + + public void putContext(Class key, Object contextObject) throws NullPointerException { + this.target.putContext(key, contextObject); + } + + public void setLocale(Locale locale) { + this.target.setLocale(locale); + } + + public void setPropertyResolved(boolean resolved) { + this.target.setPropertyResolved(resolved); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ELResolverImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ELResolverImpl.java new file mode 100644 index 000000000..0c1309502 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ELResolverImpl.java @@ -0,0 +1,146 @@ +/* + * 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.el; + +import java.util.Iterator; + +import javax.el.ArrayELResolver; +import javax.el.BeanELResolver; +import javax.el.CompositeELResolver; +import javax.el.ELContext; +import javax.el.ELException; +import javax.el.ELResolver; +import javax.el.ListELResolver; +import javax.el.MapELResolver; +import javax.el.PropertyNotFoundException; +import javax.el.PropertyNotWritableException; +import javax.el.ResourceBundleELResolver; +import javax.servlet.jsp.el.VariableResolver; + +public final class ELResolverImpl extends ELResolver { + + public final static ELResolver DefaultResolver = new CompositeELResolver(); + + static { + ((CompositeELResolver) DefaultResolver).add(new MapELResolver()); + ((CompositeELResolver) DefaultResolver).add(new ResourceBundleELResolver()); + ((CompositeELResolver) DefaultResolver).add(new ListELResolver()); + ((CompositeELResolver) DefaultResolver).add(new ArrayELResolver()); + ((CompositeELResolver) DefaultResolver).add(new BeanELResolver()); + } + + private final VariableResolver variableResolver; + + public ELResolverImpl(VariableResolver variableResolver) { + this.variableResolver = variableResolver; + } + + public Object getValue(ELContext context, Object base, Object property) + throws NullPointerException, PropertyNotFoundException, ELException { + if (context == null) { + throw new NullPointerException(); + } + + if (base == null) { + context.setPropertyResolved(true); + if (property != null) { + try { + return this.variableResolver.resolveVariable(property + .toString()); + } catch (javax.servlet.jsp.el.ELException e) { + throw new ELException(e.getMessage(), e.getCause()); + } + } + } + + if (!context.isPropertyResolved()) { + return DefaultResolver.getValue(context, base, property); + } + return null; + } + + public Class getType(ELContext context, Object base, Object property) + throws NullPointerException, PropertyNotFoundException, ELException { + if (context == null) { + throw new NullPointerException(); + } + + if (base == null) { + context.setPropertyResolved(true); + if (property != null) { + try { + Object obj = this.variableResolver.resolveVariable(property + .toString()); + return (obj != null) ? obj.getClass() : null; + } catch (javax.servlet.jsp.el.ELException e) { + throw new ELException(e.getMessage(), e.getCause()); + } + } + } + + if (!context.isPropertyResolved()) { + return DefaultResolver.getType(context, base, property); + } + return null; + } + + public void setValue(ELContext context, Object base, Object property, + Object value) throws NullPointerException, + PropertyNotFoundException, PropertyNotWritableException, + ELException { + if (context == null) { + throw new NullPointerException(); + } + + if (base == null) { + context.setPropertyResolved(true); + throw new PropertyNotWritableException( + "Legacy VariableResolver wrapped, not writable"); + } + + if (!context.isPropertyResolved()) { + DefaultResolver.setValue(context, base, property, value); + } + } + + public boolean isReadOnly(ELContext context, Object base, Object property) + throws NullPointerException, PropertyNotFoundException, ELException { + if (context == null) { + throw new NullPointerException(); + } + + if (base == null) { + context.setPropertyResolved(true); + return true; + } + + return DefaultResolver.isReadOnly(context, base, property); + } + + public Iterator getFeatureDescriptors(ELContext context, Object base) { + return DefaultResolver.getFeatureDescriptors(context, base); + } + + public Class getCommonPropertyType(ELContext context, Object base) { + if (base == null) { + return String.class; + } + return DefaultResolver.getCommonPropertyType(context, base); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ExpressionEvaluatorImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ExpressionEvaluatorImpl.java new file mode 100644 index 000000000..21dbefce3 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ExpressionEvaluatorImpl.java @@ -0,0 +1,58 @@ +/* + * 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.el; + +import javax.el.ELContext; +import javax.el.ExpressionFactory; +import javax.el.ValueExpression; +import javax.servlet.jsp.el.ELException; +import javax.servlet.jsp.el.ELParseException; +import javax.servlet.jsp.el.Expression; +import javax.servlet.jsp.el.ExpressionEvaluator; +import javax.servlet.jsp.el.FunctionMapper; +import javax.servlet.jsp.el.VariableResolver; + + +public final class ExpressionEvaluatorImpl extends ExpressionEvaluator { + + private final ExpressionFactory factory; + + public ExpressionEvaluatorImpl(ExpressionFactory factory) { + this.factory = factory; + } + + public Expression parseExpression(String expression, Class expectedType, + FunctionMapper fMapper) throws ELException { + try { + ELContextImpl ctx = new ELContextImpl(ELResolverImpl.DefaultResolver); + if (fMapper != null) { + ctx.setFunctionMapper(new FunctionMapperImpl(fMapper)); + } + ValueExpression ve = this.factory.createValueExpression(ctx, expression, expectedType); + return new ExpressionImpl(ve); + } catch (javax.el.ELException e) { + throw new ELParseException(e.getMessage()); + } + } + + public Object evaluate(String expression, Class expectedType, + VariableResolver vResolver, FunctionMapper fMapper) + throws ELException { + return this.parseExpression(expression, expectedType, fMapper).evaluate(vResolver); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ExpressionImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ExpressionImpl.java new file mode 100644 index 000000000..107326dfb --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/ExpressionImpl.java @@ -0,0 +1,38 @@ +/* + * 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.el; + +import javax.el.ELContext; +import javax.el.ValueExpression; +import javax.servlet.jsp.el.ELException; +import javax.servlet.jsp.el.Expression; +import javax.servlet.jsp.el.VariableResolver; + +public final class ExpressionImpl extends Expression { + + private final ValueExpression ve; + + public ExpressionImpl(ValueExpression ve) { + this.ve = ve; + } + + public Object evaluate(VariableResolver vResolver) throws ELException { + ELContext ctx = new ELContextImpl(new ELResolverImpl(vResolver)); + return ve.getValue(ctx); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/FunctionMapperImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/FunctionMapperImpl.java new file mode 100644 index 000000000..0cf84ecde --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/FunctionMapperImpl.java @@ -0,0 +1,35 @@ +/* + * 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.el; + +import java.lang.reflect.Method; + +import javax.servlet.jsp.el.FunctionMapper; + +public final class FunctionMapperImpl extends javax.el.FunctionMapper { + + private final FunctionMapper fnMapper; + + public FunctionMapperImpl(FunctionMapper fnMapper) { + this.fnMapper = fnMapper; + } + + public Method resolveFunction(String prefix, String localName) { + return this.fnMapper.resolveFunction(prefix, localName); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspELException.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspELException.java new file mode 100644 index 000000000..c9cf3022f --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspELException.java @@ -0,0 +1,26 @@ +/* + * 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.el; + +import javax.el.ELException; + +public class JspELException extends ELException { + + public JspELException(String mark, ELException e) { + super(mark + " " + e.getMessage(), e.getCause()); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspMethodExpression.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspMethodExpression.java new file mode 100644 index 000000000..b48e00342 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspMethodExpression.java @@ -0,0 +1,108 @@ +/* + * 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.el; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import javax.el.ELContext; +import javax.el.ELException; +import javax.el.MethodExpression; +import javax.el.MethodInfo; +import javax.el.MethodNotFoundException; +import javax.el.PropertyNotFoundException; + +public final class JspMethodExpression extends MethodExpression implements + Externalizable { + + private String mark; + + private MethodExpression target; + + public JspMethodExpression() { + super(); + } + + public JspMethodExpression(String mark, MethodExpression target) { + this.target = target; + this.mark = mark; + } + + public MethodInfo getMethodInfo(ELContext context) + throws NullPointerException, PropertyNotFoundException, + MethodNotFoundException, ELException { + try { + return this.target.getMethodInfo(context); + } catch (MethodNotFoundException e) { + if (e instanceof JspMethodNotFoundException) throw e; + throw new JspMethodNotFoundException(this.mark, e); + } catch (PropertyNotFoundException e) { + if (e instanceof JspPropertyNotFoundException) throw e; + throw new JspPropertyNotFoundException(this.mark, e); + } catch (ELException e) { + if (e instanceof JspELException) throw e; + throw new JspELException(this.mark, e); + } + } + + public Object invoke(ELContext context, Object[] params) + throws NullPointerException, PropertyNotFoundException, + MethodNotFoundException, ELException { + try { + return this.target.invoke(context, params); + } catch (MethodNotFoundException e) { + if (e instanceof JspMethodNotFoundException) throw e; + throw new JspMethodNotFoundException(this.mark, e); + } catch (PropertyNotFoundException e) { + if (e instanceof JspPropertyNotFoundException) throw e; + throw new JspPropertyNotFoundException(this.mark, e); + } catch (ELException e) { + if (e instanceof JspELException) throw e; + throw new JspELException(this.mark, e); + } + } + + public boolean equals(Object obj) { + return this.target.equals(obj); + } + + public int hashCode() { + return this.target.hashCode(); + } + + public String getExpressionString() { + return this.target.getExpressionString(); + } + + public boolean isLiteralText() { + return this.target.isLiteralText(); + } + + public void writeExternal(ObjectOutput out) throws IOException { + out.writeUTF(this.mark); + out.writeObject(this.target); + } + + public void readExternal(ObjectInput in) throws IOException, + ClassNotFoundException { + this.mark = in.readUTF(); + this.target = (MethodExpression) in.readObject(); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspMethodNotFoundException.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspMethodNotFoundException.java new file mode 100644 index 000000000..abb9d30ff --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspMethodNotFoundException.java @@ -0,0 +1,26 @@ +/* + * 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.el; + +import javax.el.MethodNotFoundException; + +public class JspMethodNotFoundException extends MethodNotFoundException { + + public JspMethodNotFoundException(String mark, MethodNotFoundException e) { + super(mark + " " + e.getMessage(), e.getCause()); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspPropertyNotFoundException.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspPropertyNotFoundException.java new file mode 100644 index 000000000..89ac40b4a --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspPropertyNotFoundException.java @@ -0,0 +1,28 @@ +/* + * 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.el; + +import javax.el.PropertyNotFoundException; + +public final class JspPropertyNotFoundException extends + PropertyNotFoundException { + + public JspPropertyNotFoundException(String mark, PropertyNotFoundException e) { + super(mark + " " + e.getMessage(), e.getCause()); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspPropertyNotWritableException.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspPropertyNotWritableException.java new file mode 100644 index 000000000..70507a4a5 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspPropertyNotWritableException.java @@ -0,0 +1,27 @@ +/* + * 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.el; + +import javax.el.PropertyNotWritableException; + +public class JspPropertyNotWritableException extends + PropertyNotWritableException { + + public JspPropertyNotWritableException(String mark, PropertyNotWritableException e) { + super(mark + " " + e.getMessage(), e.getCause()); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspValueExpression.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspValueExpression.java new file mode 100644 index 000000000..d8c32a072 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/JspValueExpression.java @@ -0,0 +1,137 @@ +/* + * 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.el; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import javax.el.ELContext; +import javax.el.ELException; +import javax.el.PropertyNotFoundException; +import javax.el.PropertyNotWritableException; +import javax.el.ValueExpression; + +/** + * Wrapper for providing context to ValueExpressions + * + * @author Jacob Hookom + */ +public final class JspValueExpression extends ValueExpression implements + Externalizable { + + private ValueExpression target; + + private String mark; + + public JspValueExpression() { + super(); + } + + public JspValueExpression(String mark, ValueExpression target) { + this.target = target; + this.mark = mark; + } + + public Class getExpectedType() { + return this.target.getExpectedType(); + } + + public Class getType(ELContext context) throws NullPointerException, + PropertyNotFoundException, ELException { + try { + return this.target.getType(context); + } catch (PropertyNotFoundException e) { + if (e instanceof JspPropertyNotFoundException) throw e; + throw new JspPropertyNotFoundException(this.mark, e); + } catch (ELException e) { + if (e instanceof JspELException) throw e; + throw new JspELException(this.mark, e); + } + } + + public boolean isReadOnly(ELContext context) throws NullPointerException, + PropertyNotFoundException, ELException { + try { + return this.target.isReadOnly(context); + } catch (PropertyNotFoundException e) { + if (e instanceof JspPropertyNotFoundException) throw e; + throw new JspPropertyNotFoundException(this.mark, e); + } catch (ELException e) { + if (e instanceof JspELException) throw e; + throw new JspELException(this.mark, e); + } + } + + public void setValue(ELContext context, Object value) + throws NullPointerException, PropertyNotFoundException, + PropertyNotWritableException, ELException { + try { + this.target.setValue(context, value); + } catch (PropertyNotWritableException e) { + if (e instanceof JspPropertyNotWritableException) throw e; + throw new JspPropertyNotWritableException(this.mark, e); + } catch (PropertyNotFoundException e) { + if (e instanceof JspPropertyNotFoundException) throw e; + throw new JspPropertyNotFoundException(this.mark, e); + } catch (ELException e) { + if (e instanceof JspELException) throw e; + throw new JspELException(this.mark, e); + } + } + + public Object getValue(ELContext context) throws NullPointerException, + PropertyNotFoundException, ELException { + try { + return this.target.getValue(context); + } catch (PropertyNotFoundException e) { + if (e instanceof JspPropertyNotFoundException) throw e; + throw new JspPropertyNotFoundException(this.mark, e); + } catch (ELException e) { + if (e instanceof JspELException) throw e; + throw new JspELException(this.mark, e); + } + } + + public boolean equals(Object obj) { + return this.target.equals(obj); + } + + public int hashCode() { + return this.target.hashCode(); + } + + public String getExpressionString() { + return this.target.getExpressionString(); + } + + public boolean isLiteralText() { + return this.target.isLiteralText(); + } + + public void writeExternal(ObjectOutput out) throws IOException { + out.writeUTF(this.mark); + out.writeObject(this.target); + } + + public void readExternal(ObjectInput in) throws IOException, + ClassNotFoundException { + this.mark = in.readUTF(); + this.target = (ValueExpression) in.readObject(); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/VariableResolverImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/VariableResolverImpl.java new file mode 100644 index 000000000..7808e0f5e --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/el/VariableResolverImpl.java @@ -0,0 +1,35 @@ +/* + * 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.el; + +import javax.el.ELContext; +import javax.servlet.jsp.el.ELException; +import javax.servlet.jsp.el.VariableResolver; + +public final class VariableResolverImpl implements VariableResolver { + + private final ELContext ctx; + + public VariableResolverImpl(ELContext ctx) { + this.ctx = ctx; + } + + public Object resolveVariable(String pName) throws ELException { + return this.ctx.getELResolver().getValue(this.ctx, null, pName); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/AnnotationHelper.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/AnnotationHelper.java new file mode 100644 index 000000000..671945b26 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/AnnotationHelper.java @@ -0,0 +1,61 @@ +/* + * 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.runtime; + +import java.lang.reflect.InvocationTargetException; + +import javax.naming.NamingException; + +import org.apache.AnnotationProcessor; + + +/** + * Verify the annotation and Process it. + * + * @author Fabien Carrion + * @author Remy Maucherat + * @version $Revision: 467222 $, $Date: 2006-10-24 05:17:11 +0200 (Tue, 24 Oct 2006) $ + */ +public class AnnotationHelper { + + + /** + * Call postConstruct method on the specified instance. Note: In Jasper, this + * calls naming resources injection as well. + */ + public static void postConstruct(AnnotationProcessor processor, Object instance) + throws IllegalAccessException, InvocationTargetException, NamingException { + if (processor != null) { + processor.processAnnotations(instance); + processor.postConstruct(instance); + } + } + + + /** + * Call preDestroy method on the specified instance. + */ + public static void preDestroy(AnnotationProcessor processor, Object instance) + throws IllegalAccessException, InvocationTargetException { + if (processor != null) { + processor.preDestroy(instance); + } + } + + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/BodyContentImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/BodyContentImpl.java new file mode 100644 index 000000000..1c2a8e797 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/BodyContentImpl.java @@ -0,0 +1,604 @@ +/* + * 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.runtime; + +import java.io.CharArrayReader; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; + +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.tagext.BodyContent; + +import org.apache.jasper.Constants; + +/** + * Write text to a character-output stream, buffering characters so as + * to provide for the efficient writing of single characters, arrays, + * and strings. + * + * Provide support for discarding for the output that has been buffered. + * + * @author Rajiv Mordani + * @author Jan Luehe + */ +public class BodyContentImpl extends BodyContent { + + private static final String LINE_SEPARATOR = + System.getProperty("line.separator"); + private static final boolean LIMIT_BUFFER = + Boolean.valueOf(System.getProperty("org.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER", "false")).booleanValue(); + + private char[] cb; + private int nextChar; + private boolean closed; + + // Enclosed writer to which any output is written + private Writer writer; + + /** + * Constructor. + */ + public BodyContentImpl(JspWriter enclosingWriter) { + super(enclosingWriter); + bufferSize = Constants.DEFAULT_TAG_BUFFER_SIZE; + cb = new char[bufferSize]; + nextChar = 0; + closed = false; + } + + /** + * Write a single character. + */ + public void write(int c) throws IOException { + if (writer != null) { + writer.write(c); + } else { + ensureOpen(); + if (nextChar >= bufferSize) { + reAllocBuff (1); + } + cb[nextChar++] = (char) c; + } + } + + /** + * Write a portion of an array of characters. + * + *

Ordinarily this method stores characters from the given array into + * this stream's buffer, flushing the buffer to the underlying stream as + * needed. If the requested length is at least as large as the buffer, + * however, then this method will flush the buffer and write the characters + * directly to the underlying stream. Thus redundant + * DiscardableBufferedWriters will not copy data + * unnecessarily. + * + * @param cbuf A character array + * @param off Offset from which to start reading characters + * @param len Number of characters to write + */ + public void write(char[] cbuf, int off, int len) throws IOException { + if (writer != null) { + writer.write(cbuf, off, len); + } else { + ensureOpen(); + + if ((off < 0) || (off > cbuf.length) || (len < 0) || + ((off + len) > cbuf.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + + if (len >= bufferSize - nextChar) + reAllocBuff (len); + + System.arraycopy(cbuf, off, cb, nextChar, len); + nextChar+=len; + } + } + + /** + * Write an array of characters. This method cannot be inherited from the + * Writer class because it must suppress I/O exceptions. + */ + public void write(char[] buf) throws IOException { + if (writer != null) { + writer.write(buf); + } else { + write(buf, 0, buf.length); + } + } + + /** + * Write a portion of a String. + * + * @param s String to be written + * @param off Offset from which to start reading characters + * @param len Number of characters to be written + */ + public void write(String s, int off, int len) throws IOException { + if (writer != null) { + writer.write(s, off, len); + } else { + ensureOpen(); + if (len >= bufferSize - nextChar) + reAllocBuff(len); + + s.getChars(off, off + len, cb, nextChar); + nextChar += len; + } + } + + /** + * Write a string. This method cannot be inherited from the Writer class + * because it must suppress I/O exceptions. + */ + public void write(String s) throws IOException { + if (writer != null) { + writer.write(s); + } else { + write(s, 0, s.length()); + } + } + + /** + * Write a line separator. The line separator string is defined by the + * system property line.separator, and is not necessarily a single + * newline ('\n') character. + * + * @throws IOException If an I/O error occurs + */ + public void newLine() throws IOException { + if (writer != null) { + writer.write(LINE_SEPARATOR); + } else { + write(LINE_SEPARATOR); + } + } + + /** + * Print a boolean value. The string produced by {@link + * java.lang.String#valueOf(boolean)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link + * #write(int)} method. + * + * @param b The boolean to be printed + * @throws IOException + */ + public void print(boolean b) throws IOException { + if (writer != null) { + writer.write(b ? "true" : "false"); + } else { + write(b ? "true" : "false"); + } + } + + /** + * Print a character. The character is translated into one or more bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link + * #write(int)} method. + * + * @param c The char to be printed + * @throws IOException + */ + public void print(char c) throws IOException { + if (writer != null) { + writer.write(String.valueOf(c)); + } else { + write(String.valueOf(c)); + } + } + + /** + * Print an integer. The string produced by {@link + * java.lang.String#valueOf(int)} is translated into bytes according + * to the platform's default character encoding, and these bytes are + * written in exactly the manner of the {@link #write(int)} + * method. + * + * @param i The int to be printed + * @throws IOException + */ + public void print(int i) throws IOException { + if (writer != null) { + writer.write(String.valueOf(i)); + } else { + write(String.valueOf(i)); + } + } + + /** + * Print a long integer. The string produced by {@link + * java.lang.String#valueOf(long)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param l The long to be printed + * @throws IOException + */ + public void print(long l) throws IOException { + if (writer != null) { + writer.write(String.valueOf(l)); + } else { + write(String.valueOf(l)); + } + } + + /** + * Print a floating-point number. The string produced by {@link + * java.lang.String#valueOf(float)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param f The float to be printed + * @throws IOException + */ + public void print(float f) throws IOException { + if (writer != null) { + writer.write(String.valueOf(f)); + } else { + write(String.valueOf(f)); + } + } + + /** + * Print a double-precision floating-point number. The string produced by + * {@link java.lang.String#valueOf(double)} is translated into + * bytes according to the platform's default character encoding, and these + * bytes are written in exactly the manner of the {@link + * #write(int)} method. + * + * @param d The double to be printed + * @throws IOException + */ + public void print(double d) throws IOException { + if (writer != null) { + writer.write(String.valueOf(d)); + } else { + write(String.valueOf(d)); + } + } + + /** + * Print an array of characters. The characters are converted into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param s The array of chars to be printed + * + * @throws NullPointerException If s is null + * @throws IOException + */ + public void print(char[] s) throws IOException { + if (writer != null) { + writer.write(s); + } else { + write(s); + } + } + + /** + * Print a string. If the argument is null then the string + * "null" is printed. Otherwise, the string's characters are + * converted into bytes according to the platform's default character + * encoding, and these bytes are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param s The String to be printed + * @throws IOException + */ + public void print(String s) throws IOException { + if (s == null) s = "null"; + if (writer != null) { + writer.write(s); + } else { + write(s); + } + } + + /** + * Print an object. The string produced by the {@link + * java.lang.String#valueOf(Object)} method is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param obj The Object to be printed + * @throws IOException + */ + public void print(Object obj) throws IOException { + if (writer != null) { + writer.write(String.valueOf(obj)); + } else { + write(String.valueOf(obj)); + } + } + + /** + * Terminate the current line by writing the line separator string. The + * line separator string is defined by the system property + * line.separator, and is not necessarily a single newline + * character ('\n'). + * + * @throws IOException + */ + public void println() throws IOException { + newLine(); + } + + /** + * Print a boolean value and then terminate the line. This method behaves + * as though it invokes {@link #print(boolean)} and then + * {@link #println()}. + * + * @throws IOException + */ + public void println(boolean x) throws IOException { + print(x); + println(); + } + + /** + * Print a character and then terminate the line. This method behaves as + * though it invokes {@link #print(char)} and then + * {@link #println()}. + * + * @throws IOException + */ + public void println(char x) throws IOException { + print(x); + println(); + } + + /** + * Print an integer and then terminate the line. This method behaves as + * though it invokes {@link #print(int)} and then + * {@link #println()}. + * + * @throws IOException + */ + public void println(int x) throws IOException { + print(x); + println(); + } + + /** + * Print a long integer and then terminate the line. This method behaves + * as though it invokes {@link #print(long)} and then + * {@link #println()}. + * + * @throws IOException + */ + public void println(long x) throws IOException { + print(x); + println(); + } + + /** + * Print a floating-point number and then terminate the line. This method + * behaves as though it invokes {@link #print(float)} and then + * {@link #println()}. + * + * @throws IOException + */ + public void println(float x) throws IOException { + print(x); + println(); + } + + /** + * Print a double-precision floating-point number and then terminate the + * line. This method behaves as though it invokes {@link + * #print(double)} and then {@link #println()}. + * + * @throws IOException + */ + public void println(double x) throws IOException{ + print(x); + println(); + } + + /** + * Print an array of characters and then terminate the line. This method + * behaves as though it invokes {@link #print(char[])} and + * then {@link #println()}. + * + * @throws IOException + */ + public void println(char x[]) throws IOException { + print(x); + println(); + } + + /** + * Print a String and then terminate the line. This method behaves as + * though it invokes {@link #print(String)} and then + * {@link #println()}. + * + * @throws IOException + */ + public void println(String x) throws IOException { + print(x); + println(); + } + + /** + * Print an Object and then terminate the line. This method behaves as + * though it invokes {@link #print(Object)} and then + * {@link #println()}. + * + * @throws IOException + */ + public void println(Object x) throws IOException { + print(x); + println(); + } + + /** + * Clear the contents of the buffer. If the buffer has been already + * been flushed then the clear operation shall throw an IOException + * to signal the fact that some data has already been irrevocably + * written to the client response stream. + * + * @throws IOException If an I/O error occurs + */ + public void clear() throws IOException { + if (writer != null) { + throw new IOException(); + } else { + nextChar = 0; + if (LIMIT_BUFFER && (cb.length > Constants.DEFAULT_TAG_BUFFER_SIZE)) { + bufferSize = Constants.DEFAULT_TAG_BUFFER_SIZE; + cb = new char[bufferSize]; + } + } + } + + /** + * Clears the current contents of the buffer. Unlike clear(), this + * mehtod will not throw an IOException if the buffer has already been + * flushed. It merely clears the current content of the buffer and + * returns. + * + * @throws IOException If an I/O error occurs + */ + public void clearBuffer() throws IOException { + if (writer == null) { + this.clear(); + } + } + + /** + * Close the stream, flushing it first. Once a stream has been closed, + * further write() or flush() invocations will cause an IOException to be + * thrown. Closing a previously-closed stream, however, has no effect. + * + * @throws IOException If an I/O error occurs + */ + public void close() throws IOException { + if (writer != null) { + writer.close(); + } else { + closed = true; + } + } + + /** + * This method returns the size of the buffer used by the JspWriter. + * + * @return the size of the buffer in bytes, or 0 is unbuffered. + */ + public int getBufferSize() { + // According to the spec, the JspWriter returned by + // JspContext.pushBody(java.io.Writer writer) must behave as + // though it were unbuffered. This means that its getBufferSize() + // must always return 0. + return (writer == null) ? bufferSize : 0; + } + + /** + * @return the number of bytes unused in the buffer + */ + public int getRemaining() { + return (writer == null) ? bufferSize-nextChar : 0; + } + + /** + * Return the value of this BodyJspWriter as a Reader. + * Note: this is after evaluation!! There are no scriptlets, + * etc in this stream. + * + * @return the value of this BodyJspWriter as a Reader + */ + public Reader getReader() { + return (writer == null) ? new CharArrayReader (cb, 0, nextChar) : null; + } + + /** + * Return the value of the BodyJspWriter as a String. + * Note: this is after evaluation!! There are no scriptlets, + * etc in this stream. + * + * @return the value of the BodyJspWriter as a String + */ + public String getString() { + return (writer == null) ? new String(cb, 0, nextChar) : null; + } + + /** + * Write the contents of this BodyJspWriter into a Writer. + * Subclasses are likely to do interesting things with the + * implementation so some things are extra efficient. + * + * @param out The writer into which to place the contents of this body + * evaluation + */ + public void writeOut(Writer out) throws IOException { + if (writer == null) { + out.write(cb, 0, nextChar); + // Flush not called as the writer passed could be a BodyContent and + // it doesn't allow to flush. + } + } + + /** + * Sets the writer to which all output is written. + */ + void setWriter(Writer writer) { + this.writer = writer; + closed = false; + if (writer == null) { + clearBody(); + } + } + + private void ensureOpen() throws IOException { + if (closed) throw new IOException("Stream closed"); + } + + /** + * Reallocates buffer since the spec requires it to be unbounded. + */ + private void reAllocBuff(int len) { + + if (bufferSize + len <= cb.length) { + bufferSize = cb.length; + return; + } + + if (len < cb.length) { + len = cb.length; + } + + bufferSize = cb.length + len; + char[] tmp = new char[bufferSize]; + + System.arraycopy(cb, 0, tmp, 0, cb.length); + cb = tmp; + tmp = null; + + } + + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/HttpJspBase.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/HttpJspBase.java new file mode 100644 index 000000000..a1b6413aa --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/HttpJspBase.java @@ -0,0 +1,88 @@ +/* + * 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.runtime; + +import java.io.IOException; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.jsp.HttpJspPage; +import javax.servlet.jsp.JspFactory; + +import org.apache.jasper.compiler.Localizer; + +/** + * This is the super class of all JSP-generated servlets. + * + * @author Anil K. Vijendran + */ +public abstract class HttpJspBase + extends HttpServlet + implements HttpJspPage + + +{ + + protected HttpJspBase() { + } + + public final void init(ServletConfig config) + throws ServletException + { + super.init(config); + jspInit(); + _jspInit(); + } + + public String getServletInfo() { + return Localizer.getMessage("jsp.engine.info"); + } + + public final void destroy() { + jspDestroy(); + _jspDestroy(); + } + + /** + * Entry point into service. + */ + public final void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + _jspService(request, response); + } + + public void jspInit() { + } + + public void _jspInit() { + } + + public void jspDestroy() { + } + + protected void _jspDestroy() { + } + + public abstract void _jspService(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException; +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspApplicationContextImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspApplicationContextImpl.java new file mode 100644 index 000000000..1ea649aa6 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspApplicationContextImpl.java @@ -0,0 +1,138 @@ +/* + * 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.runtime; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.el.ArrayELResolver; +import javax.el.BeanELResolver; +import javax.el.CompositeELResolver; +import javax.el.ELContextEvent; +import javax.el.ELContextListener; +import javax.el.ELResolver; +import javax.el.ExpressionFactory; +import javax.el.ListELResolver; +import javax.el.MapELResolver; +import javax.el.ResourceBundleELResolver; +import javax.servlet.ServletContext; +import javax.servlet.jsp.JspApplicationContext; +import javax.servlet.jsp.JspContext; +import javax.servlet.jsp.el.ImplicitObjectELResolver; +import javax.servlet.jsp.el.ScopedAttributeELResolver; + +import org.apache.el.ExpressionFactoryImpl; +import org.apache.jasper.el.ELContextImpl; + +/** + * Implementation of JspApplicationContext + * + * @author Jacob Hookom + */ +public class JspApplicationContextImpl implements JspApplicationContext { + + private final static String KEY = JspApplicationContextImpl.class.getName(); + + private final static ExpressionFactory expressionFactory = new ExpressionFactoryImpl(); + + private final List contextListeners = new ArrayList(); + + private final List resolvers = new ArrayList(); + + private boolean instantiated = false; + + private ELResolver resolver; + + public JspApplicationContextImpl() { + + } + + public void addELContextListener(ELContextListener listener) { + if (listener == null) { + throw new IllegalArgumentException("ELConextListener was null"); + } + this.contextListeners.add(listener); + } + + public static JspApplicationContextImpl getInstance(ServletContext context) { + if (context == null) { + throw new IllegalArgumentException("ServletContext was null"); + } + JspApplicationContextImpl impl = (JspApplicationContextImpl) context + .getAttribute(KEY); + if (impl == null) { + impl = new JspApplicationContextImpl(); + context.setAttribute(KEY, impl); + } + return impl; + } + + public ELContextImpl createELContext(JspContext context) { + if (context == null) { + throw new IllegalArgumentException("JspContext was null"); + } + + // create ELContext for JspContext + ELResolver r = this.createELResolver(); + ELContextImpl ctx = new ELContextImpl(r); + ctx.putContext(JspContext.class, context); + + // alert all ELContextListeners + ELContextEvent event = new ELContextEvent(ctx); + for (int i = 0; i < this.contextListeners.size(); i++) { + this.contextListeners.get(i).contextCreated(event); + } + + return ctx; + } + + private ELResolver createELResolver() { + this.instantiated = true; + if (this.resolver == null) { + CompositeELResolver r = new CompositeELResolver(); + r.add(new ImplicitObjectELResolver()); + for (Iterator itr = this.resolvers.iterator(); itr.hasNext();) { + r.add((ELResolver) itr.next()); + } + r.add(new MapELResolver()); + r.add(new ResourceBundleELResolver()); + r.add(new ListELResolver()); + r.add(new ArrayELResolver()); + r.add(new BeanELResolver()); + r.add(new ScopedAttributeELResolver()); + this.resolver = r; + } + return this.resolver; + } + + public void addELResolver(ELResolver resolver) throws IllegalStateException { + if (resolver == null) { + throw new IllegalArgumentException("ELResolver was null"); + } + if (this.instantiated) { + throw new IllegalStateException( + "cannot call addELResolver after the first request has been made"); + } + this.resolvers.add(resolver); + } + + public ExpressionFactory getExpressionFactory() { + return expressionFactory; + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspContextWrapper.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspContextWrapper.java new file mode 100644 index 000000000..05f663836 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspContextWrapper.java @@ -0,0 +1,462 @@ +/* + * 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.runtime; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.el.ELContext; +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.jsp.JspContext; +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.PageContext; +import javax.servlet.jsp.el.ELException; +import javax.servlet.jsp.el.ExpressionEvaluator; +import javax.servlet.jsp.el.VariableResolver; +import javax.servlet.jsp.tagext.BodyContent; +import javax.servlet.jsp.tagext.VariableInfo; + +import org.apache.jasper.compiler.Localizer; +import org.apache.jasper.util.Enumerator; + +/** + * Implementation of a JSP Context Wrapper. + * + * The JSP Context Wrapper is a JspContext created and maintained by a tag + * handler implementation. It wraps the Invoking JSP Context, that is, the + * JspContext instance passed to the tag handler by the invoking page via + * setJspContext(). + * + * @author Kin-man Chung + * @author Jan Luehe + * @author Jacob Hookom + */ +public class JspContextWrapper extends PageContext implements VariableResolver { + + // Invoking JSP context + private PageContext invokingJspCtxt; + + private transient HashMap pageAttributes; + + // ArrayList of NESTED scripting variables + private ArrayList nestedVars; + + // ArrayList of AT_BEGIN scripting variables + private ArrayList atBeginVars; + + // ArrayList of AT_END scripting variables + private ArrayList atEndVars; + + private Map aliases; + + private HashMap originalNestedVars; + + public JspContextWrapper(JspContext jspContext, ArrayList nestedVars, + ArrayList atBeginVars, ArrayList atEndVars, Map aliases) { + this.invokingJspCtxt = (PageContext) jspContext; + this.nestedVars = nestedVars; + this.atBeginVars = atBeginVars; + this.atEndVars = atEndVars; + this.pageAttributes = new HashMap(16); + this.aliases = aliases; + + if (nestedVars != null) { + this.originalNestedVars = new HashMap(nestedVars.size()); + } + syncBeginTagFile(); + } + + public void initialize(Servlet servlet, ServletRequest request, + ServletResponse response, String errorPageURL, + boolean needsSession, int bufferSize, boolean autoFlush) + throws IOException, IllegalStateException, IllegalArgumentException { + } + + public Object getAttribute(String name) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + return pageAttributes.get(name); + } + + public Object getAttribute(String name, int scope) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + if (scope == PAGE_SCOPE) { + return pageAttributes.get(name); + } + + return invokingJspCtxt.getAttribute(name, scope); + } + + public void setAttribute(String name, Object value) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + if (value != null) { + pageAttributes.put(name, value); + } else { + removeAttribute(name, PAGE_SCOPE); + } + } + + public void setAttribute(String name, Object value, int scope) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + if (scope == PAGE_SCOPE) { + if (value != null) { + pageAttributes.put(name, value); + } else { + removeAttribute(name, PAGE_SCOPE); + } + } else { + invokingJspCtxt.setAttribute(name, value, scope); + } + } + + public Object findAttribute(String name) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + Object o = pageAttributes.get(name); + if (o == null) { + o = invokingJspCtxt.getAttribute(name, REQUEST_SCOPE); + if (o == null) { + if (getSession() != null) { + o = invokingJspCtxt.getAttribute(name, SESSION_SCOPE); + } + if (o == null) { + o = invokingJspCtxt.getAttribute(name, APPLICATION_SCOPE); + } + } + } + + return o; + } + + public void removeAttribute(String name) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + pageAttributes.remove(name); + invokingJspCtxt.removeAttribute(name, REQUEST_SCOPE); + if (getSession() != null) { + invokingJspCtxt.removeAttribute(name, SESSION_SCOPE); + } + invokingJspCtxt.removeAttribute(name, APPLICATION_SCOPE); + } + + public void removeAttribute(String name, int scope) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + if (scope == PAGE_SCOPE) { + pageAttributes.remove(name); + } else { + invokingJspCtxt.removeAttribute(name, scope); + } + } + + public int getAttributesScope(String name) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + if (pageAttributes.get(name) != null) { + return PAGE_SCOPE; + } else { + return invokingJspCtxt.getAttributesScope(name); + } + } + + public Enumeration getAttributeNamesInScope(int scope) { + if (scope == PAGE_SCOPE) { + return new Enumerator(pageAttributes.keySet().iterator()); + } + + return invokingJspCtxt.getAttributeNamesInScope(scope); + } + + public void release() { + invokingJspCtxt.release(); + } + + public JspWriter getOut() { + return invokingJspCtxt.getOut(); + } + + public HttpSession getSession() { + return invokingJspCtxt.getSession(); + } + + public Object getPage() { + return invokingJspCtxt.getPage(); + } + + public ServletRequest getRequest() { + return invokingJspCtxt.getRequest(); + } + + public ServletResponse getResponse() { + return invokingJspCtxt.getResponse(); + } + + public Exception getException() { + return invokingJspCtxt.getException(); + } + + public ServletConfig getServletConfig() { + return invokingJspCtxt.getServletConfig(); + } + + public ServletContext getServletContext() { + return invokingJspCtxt.getServletContext(); + } + + public void forward(String relativeUrlPath) throws ServletException, + IOException { + invokingJspCtxt.forward(relativeUrlPath); + } + + public void include(String relativeUrlPath) throws ServletException, + IOException { + invokingJspCtxt.include(relativeUrlPath); + } + + public void include(String relativeUrlPath, boolean flush) + throws ServletException, IOException { + invokingJspCtxt.include(relativeUrlPath, false); + } + + public VariableResolver getVariableResolver() { + return this; + } + + public BodyContent pushBody() { + return invokingJspCtxt.pushBody(); + } + + public JspWriter pushBody(Writer writer) { + return invokingJspCtxt.pushBody(writer); + } + + public JspWriter popBody() { + return invokingJspCtxt.popBody(); + } + + public ExpressionEvaluator getExpressionEvaluator() { + return invokingJspCtxt.getExpressionEvaluator(); + } + + public void handlePageException(Exception ex) throws IOException, + ServletException { + // Should never be called since handleException() called with a + // Throwable in the generated servlet. + handlePageException((Throwable) ex); + } + + public void handlePageException(Throwable t) throws IOException, + ServletException { + invokingJspCtxt.handlePageException(t); + } + + /** + * VariableResolver interface + */ + public Object resolveVariable(String pName) throws ELException { + ELContext ctx = this.getELContext(); + return ctx.getELResolver().getValue(ctx, null, pName); + } + + /** + * Synchronize variables at begin of tag file + */ + public void syncBeginTagFile() { + saveNestedVariables(); + } + + /** + * Synchronize variables before fragment invokation + */ + public void syncBeforeInvoke() { + copyTagToPageScope(VariableInfo.NESTED); + copyTagToPageScope(VariableInfo.AT_BEGIN); + } + + /** + * Synchronize variables at end of tag file + */ + public void syncEndTagFile() { + copyTagToPageScope(VariableInfo.AT_BEGIN); + copyTagToPageScope(VariableInfo.AT_END); + restoreNestedVariables(); + } + + /** + * Copies the variables of the given scope from the virtual page scope of + * this JSP context wrapper to the page scope of the invoking JSP context. + * + * @param scope + * variable scope (one of NESTED, AT_BEGIN, or AT_END) + */ + private void copyTagToPageScope(int scope) { + Iterator iter = null; + + switch (scope) { + case VariableInfo.NESTED: + if (nestedVars != null) { + iter = nestedVars.iterator(); + } + break; + case VariableInfo.AT_BEGIN: + if (atBeginVars != null) { + iter = atBeginVars.iterator(); + } + break; + case VariableInfo.AT_END: + if (atEndVars != null) { + iter = atEndVars.iterator(); + } + break; + } + + while ((iter != null) && iter.hasNext()) { + String varName = (String) iter.next(); + Object obj = getAttribute(varName); + varName = findAlias(varName); + if (obj != null) { + invokingJspCtxt.setAttribute(varName, obj); + } else { + invokingJspCtxt.removeAttribute(varName, PAGE_SCOPE); + } + } + } + + /** + * Saves the values of any NESTED variables that are present in the invoking + * JSP context, so they can later be restored. + */ + private void saveNestedVariables() { + if (nestedVars != null) { + Iterator iter = nestedVars.iterator(); + while (iter.hasNext()) { + String varName = (String) iter.next(); + varName = findAlias(varName); + Object obj = invokingJspCtxt.getAttribute(varName); + if (obj != null) { + originalNestedVars.put(varName, obj); + } + } + } + } + + /** + * Restores the values of any NESTED variables in the invoking JSP context. + */ + private void restoreNestedVariables() { + if (nestedVars != null) { + Iterator iter = nestedVars.iterator(); + while (iter.hasNext()) { + String varName = (String) iter.next(); + varName = findAlias(varName); + Object obj = originalNestedVars.get(varName); + if (obj != null) { + invokingJspCtxt.setAttribute(varName, obj); + } else { + invokingJspCtxt.removeAttribute(varName, PAGE_SCOPE); + } + } + } + } + + /** + * Checks to see if the given variable name is used as an alias, and if so, + * returns the variable name for which it is used as an alias. + * + * @param varName + * The variable name to check + * @return The variable name for which varName is used as an alias, or + * varName if it is not being used as an alias + */ + private String findAlias(String varName) { + + if (aliases == null) + return varName; + + String alias = (String) aliases.get(varName); + if (alias == null) { + return varName; + } + return alias; + } + + //private ELContextImpl elContext; + + public ELContext getELContext() { + // instead decorate!!! + + return this.invokingJspCtxt.getELContext(); + + /* + if (this.elContext != null) { + JspFactory jspFact = JspFactory.getDefaultFactory(); + ServletContext servletContext = this.getServletContext(); + JspApplicationContextImpl jspCtx = (JspApplicationContextImpl) jspFact + .getJspApplicationContext(servletContext); + this.elContext = jspCtx.createELContext(this); + } + return this.elContext; + */ + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspFactoryImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspFactoryImpl.java new file mode 100644 index 000000000..f06a49d54 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspFactoryImpl.java @@ -0,0 +1,202 @@ +/* + * 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.runtime; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.jsp.JspApplicationContext; +import javax.servlet.jsp.JspEngineInfo; +import javax.servlet.jsp.JspFactory; +import javax.servlet.jsp.PageContext; + +import org.apache.jasper.Constants; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +/** + * Implementation of JspFactory. + * + * @author Anil K. Vijendran + */ +public class JspFactoryImpl extends JspFactory { + + // Logger + private Log log = LogFactory.getLog(JspFactoryImpl.class); + + private static final String SPEC_VERSION = "2.1"; + private static final boolean USE_POOL = + Boolean.valueOf(System.getProperty("org.apache.jasper.runtime.JspFactoryImpl.USE_POOL", "true")).booleanValue(); + private static final int POOL_SIZE = + Integer.valueOf(System.getProperty("org.apache.jasper.runtime.JspFactoryImpl.POOL_SIZE", "8")).intValue(); + + private ThreadLocal localPool = new ThreadLocal(); + + public PageContext getPageContext(Servlet servlet, ServletRequest request, + ServletResponse response, String errorPageURL, boolean needsSession, + int bufferSize, boolean autoflush) { + + if( Constants.IS_SECURITY_ENABLED ) { + PrivilegedGetPageContext dp = new PrivilegedGetPageContext( + (JspFactoryImpl)this, servlet, request, response, errorPageURL, + needsSession, bufferSize, autoflush); + return (PageContext)AccessController.doPrivileged(dp); + } else { + return internalGetPageContext(servlet, request, response, + errorPageURL, needsSession, + bufferSize, autoflush); + } + } + + public void releasePageContext(PageContext pc) { + if( pc == null ) + return; + if( Constants.IS_SECURITY_ENABLED ) { + PrivilegedReleasePageContext dp = new PrivilegedReleasePageContext( + (JspFactoryImpl)this,pc); + AccessController.doPrivileged(dp); + } else { + internalReleasePageContext(pc); + } + } + + public JspEngineInfo getEngineInfo() { + return new JspEngineInfo() { + public String getSpecificationVersion() { + return SPEC_VERSION; + } + }; + } + + private PageContext internalGetPageContext(Servlet servlet, ServletRequest request, + ServletResponse response, String errorPageURL, boolean needsSession, + int bufferSize, boolean autoflush) { + try { + PageContext pc; + if (USE_POOL) { + PageContextPool pool = localPool.get(); + if (pool == null) { + pool = new PageContextPool(); + localPool.set(pool); + } + pc = pool.get(); + if (pc == null) { + pc = new PageContextImpl(); + } + } else { + pc = new PageContextImpl(); + } + pc.initialize(servlet, request, response, errorPageURL, + needsSession, bufferSize, autoflush); + return pc; + } catch (Throwable ex) { + /* FIXME: need to do something reasonable here!! */ + log.fatal("Exception initializing page context", ex); + return null; + } + } + + private void internalReleasePageContext(PageContext pc) { + pc.release(); + if (USE_POOL && (pc instanceof PageContextImpl)) { + localPool.get().put(pc); + } + } + + private class PrivilegedGetPageContext implements PrivilegedAction { + + private JspFactoryImpl factory; + private Servlet servlet; + private ServletRequest request; + private ServletResponse response; + private String errorPageURL; + private boolean needsSession; + private int bufferSize; + private boolean autoflush; + + PrivilegedGetPageContext(JspFactoryImpl factory, Servlet servlet, + ServletRequest request, ServletResponse response, String errorPageURL, + boolean needsSession, int bufferSize, boolean autoflush) { + this.factory = factory; + this.servlet = servlet; + this.request = request; + this.response = response; + this.errorPageURL = errorPageURL; + this.needsSession = needsSession; + this.bufferSize = bufferSize; + this.autoflush = autoflush; + } + + public Object run() { + return factory.internalGetPageContext(servlet, request, response, + errorPageURL, needsSession, bufferSize, autoflush); + } + } + + private class PrivilegedReleasePageContext implements PrivilegedAction { + + private JspFactoryImpl factory; + private PageContext pageContext; + + PrivilegedReleasePageContext(JspFactoryImpl factory, + PageContext pageContext) { + this.factory = factory; + this.pageContext = pageContext; + } + + public Object run() { + factory.internalReleasePageContext(pageContext); + return null; + } + } + + protected final class PageContextPool { + + private PageContext[] pool; + + private int current = -1; + + public PageContextPool() { + this.pool = new PageContext[POOL_SIZE]; + } + + public void put(PageContext o) { + if (current < (POOL_SIZE - 1)) { + current++; + pool[current] = o; + } + } + + public PageContext get() { + PageContext item = null; + if (current >= 0) { + item = pool[current]; + current--; + } + return item; + } + + } + + public JspApplicationContext getJspApplicationContext(ServletContext context) { + return JspApplicationContextImpl.getInstance(context); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspFragmentHelper.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspFragmentHelper.java new file mode 100644 index 000000000..38d26d34b --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspFragmentHelper.java @@ -0,0 +1,65 @@ +/* + * 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.runtime; + +import javax.servlet.jsp.JspContext; +import javax.servlet.jsp.PageContext; +import javax.servlet.jsp.tagext.JspFragment; +import javax.servlet.jsp.tagext.JspTag; + +/** + * Helper class from which all Jsp Fragment helper classes extend. + * This class allows for the emulation of numerous fragments within + * a single class, which in turn reduces the load on the class loader + * since there are potentially many JspFragments in a single page. + *

+ * The class also provides various utility methods for JspFragment + * implementations. + * + * @author Mark Roth + */ +public abstract class JspFragmentHelper + extends JspFragment +{ + + protected int discriminator; + protected JspContext jspContext; + protected PageContext _jspx_page_context; + protected JspTag parentTag; + + public JspFragmentHelper( int discriminator, JspContext jspContext, + JspTag parentTag ) + { + this.discriminator = discriminator; + this.jspContext = jspContext; + this._jspx_page_context = null; + if( jspContext instanceof PageContext ) { + _jspx_page_context = (PageContext)jspContext; + } + this.parentTag = parentTag; + } + + public JspContext getJspContext() { + return this.jspContext; + } + + public JspTag getParentTag() { + return this.parentTag; + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspRuntimeLibrary.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspRuntimeLibrary.java new file mode 100644 index 000000000..02d21ddf8 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspRuntimeLibrary.java @@ -0,0 +1,1047 @@ +/* + * 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.runtime; + +import java.beans.PropertyEditor; +import java.beans.PropertyEditorManager; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Enumeration; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.PageContext; +import javax.servlet.jsp.tagext.BodyContent; + +import org.apache.jasper.Constants; +import org.apache.jasper.JasperException; +import org.apache.jasper.compiler.Localizer; + +/** + * Bunch of util methods that are used by code generated for useBean, + * getProperty and setProperty. + * + * The __begin, __end stuff is there so that the JSP engine can + * actually parse this file and inline them if people don't want + * runtime dependencies on this class. However, I'm not sure if that + * works so well right now. It got forgotten at some point. -akv + * + * @author Mandar Raje + * @author Shawn Bayern + */ +public class JspRuntimeLibrary { + + private static final String SERVLET_EXCEPTION + = "javax.servlet.error.exception"; + private static final String JSP_EXCEPTION + = "javax.servlet.jsp.jspException"; + + protected static class PrivilegedIntrospectHelper + implements PrivilegedExceptionAction { + + private Object bean; + private String prop; + private String value; + private ServletRequest request; + private String param; + private boolean ignoreMethodNF; + + PrivilegedIntrospectHelper(Object bean, String prop, + String value, ServletRequest request, + String param, boolean ignoreMethodNF) + { + this.bean = bean; + this.prop = prop; + this.value = value; + this.request = request; + this.param = param; + this.ignoreMethodNF = ignoreMethodNF; + } + + public Object run() throws JasperException { + internalIntrospecthelper( + bean,prop,value,request,param,ignoreMethodNF); + return null; + } + } + + /** + * Returns the value of the javax.servlet.error.exception request + * attribute value, if present, otherwise the value of the + * javax.servlet.jsp.jspException request attribute value. + * + * This method is called at the beginning of the generated servlet code + * for a JSP error page, when the "exception" implicit scripting language + * variable is initialized. + */ + public static Throwable getThrowable(ServletRequest request) { + Throwable error = (Throwable) request.getAttribute(SERVLET_EXCEPTION); + if (error == null) { + error = (Throwable) request.getAttribute(JSP_EXCEPTION); + if (error != null) { + /* + * The only place that sets JSP_EXCEPTION is + * PageContextImpl.handlePageException(). It really should set + * SERVLET_EXCEPTION, but that would interfere with the + * ErrorReportValve. Therefore, if JSP_EXCEPTION is set, we + * need to set SERVLET_EXCEPTION. + */ + request.setAttribute(SERVLET_EXCEPTION, error); + } + } + + return error; + } + + public static boolean coerceToBoolean(String s) { + if (s == null || s.length() == 0) + return false; + else + return Boolean.valueOf(s).booleanValue(); + } + + public static byte coerceToByte(String s) { + if (s == null || s.length() == 0) + return (byte) 0; + else + return Byte.valueOf(s).byteValue(); + } + + public static char coerceToChar(String s) { + if (s == null || s.length() == 0) { + return (char) 0; + } else { + // this trick avoids escaping issues + return (char)(int) s.charAt(0); + } + } + + public static double coerceToDouble(String s) { + if (s == null || s.length() == 0) + return (double) 0; + else + return Double.valueOf(s).doubleValue(); + } + + public static float coerceToFloat(String s) { + if (s == null || s.length() == 0) + return (float) 0; + else + return Float.valueOf(s).floatValue(); + } + + public static int coerceToInt(String s) { + if (s == null || s.length() == 0) + return 0; + else + return Integer.valueOf(s).intValue(); + } + + public static short coerceToShort(String s) { + if (s == null || s.length() == 0) + return (short) 0; + else + return Short.valueOf(s).shortValue(); + } + + public static long coerceToLong(String s) { + if (s == null || s.length() == 0) + return (long) 0; + else + return Long.valueOf(s).longValue(); + } + + public static Object coerce(String s, Class target) { + + boolean isNullOrEmpty = (s == null || s.length() == 0); + + if (target == Boolean.class) { + if (isNullOrEmpty) { + s = "false"; + } + return new Boolean(s); + } else if (target == Byte.class) { + if (isNullOrEmpty) + return new Byte((byte) 0); + else + return new Byte(s); + } else if (target == Character.class) { + if (isNullOrEmpty) + return new Character((char) 0); + else + return new Character(s.charAt(0)); + } else if (target == Double.class) { + if (isNullOrEmpty) + return new Double(0); + else + return new Double(s); + } else if (target == Float.class) { + if (isNullOrEmpty) + return new Float(0); + else + return new Float(s); + } else if (target == Integer.class) { + if (isNullOrEmpty) + return new Integer(0); + else + return new Integer(s); + } else if (target == Short.class) { + if (isNullOrEmpty) + return new Short((short) 0); + else + return new Short(s); + } else if (target == Long.class) { + if (isNullOrEmpty) + return new Long(0); + else + return new Long(s); + } else { + return null; + } + } + + // __begin convertMethod + public static Object convert(String propertyName, String s, Class t, + Class propertyEditorClass) + throws JasperException + { + try { + if (s == null) { + if (t.equals(Boolean.class) || t.equals(Boolean.TYPE)) + s = "false"; + else + return null; + } + if (propertyEditorClass != null) { + return getValueFromBeanInfoPropertyEditor( + t, propertyName, s, propertyEditorClass); + } else if ( t.equals(Boolean.class) || t.equals(Boolean.TYPE) ) { + if (s.equalsIgnoreCase("on") || s.equalsIgnoreCase("true")) + s = "true"; + else + s = "false"; + return new Boolean(s); + } else if ( t.equals(Byte.class) || t.equals(Byte.TYPE) ) { + return new Byte(s); + } else if (t.equals(Character.class) || t.equals(Character.TYPE)) { + return s.length() > 0 ? new Character(s.charAt(0)) : null; + } else if ( t.equals(Short.class) || t.equals(Short.TYPE) ) { + return new Short(s); + } else if ( t.equals(Integer.class) || t.equals(Integer.TYPE) ) { + return new Integer(s); + } else if ( t.equals(Float.class) || t.equals(Float.TYPE) ) { + return new Float(s); + } else if ( t.equals(Long.class) || t.equals(Long.TYPE) ) { + return new Long(s); + } else if ( t.equals(Double.class) || t.equals(Double.TYPE) ) { + return new Double(s); + } else if ( t.equals(String.class) ) { + return s; + } else if ( t.equals(java.io.File.class) ) { + return new java.io.File(s); + } else if (t.getName().equals("java.lang.Object")) { + return new Object[] {s}; + } else { + return getValueFromPropertyEditorManager( + t, propertyName, s); + } + } catch (Exception ex) { + throw new JasperException(ex); + } + } + // __end convertMethod + + // __begin introspectMethod + public static void introspect(Object bean, ServletRequest request) + throws JasperException + { + Enumeration e = request.getParameterNames(); + while ( e.hasMoreElements() ) { + String name = (String) e.nextElement(); + String value = request.getParameter(name); + introspecthelper(bean, name, value, request, name, true); + } + } + // __end introspectMethod + + // __begin introspecthelperMethod + public static void introspecthelper(Object bean, String prop, + String value, ServletRequest request, + String param, boolean ignoreMethodNF) + throws JasperException + { + if( Constants.IS_SECURITY_ENABLED ) { + try { + PrivilegedIntrospectHelper dp = + new PrivilegedIntrospectHelper( + bean,prop,value,request,param,ignoreMethodNF); + AccessController.doPrivileged(dp); + } catch( PrivilegedActionException pe) { + Exception e = pe.getException(); + throw (JasperException)e; + } + } else { + internalIntrospecthelper( + bean,prop,value,request,param,ignoreMethodNF); + } + } + + private static void internalIntrospecthelper(Object bean, String prop, + String value, ServletRequest request, + String param, boolean ignoreMethodNF) + throws JasperException + { + Method method = null; + Class type = null; + Class propertyEditorClass = null; + try { + java.beans.BeanInfo info + = java.beans.Introspector.getBeanInfo(bean.getClass()); + if ( info != null ) { + java.beans.PropertyDescriptor pd[] + = info.getPropertyDescriptors(); + for (int i = 0 ; i < pd.length ; i++) { + if ( pd[i].getName().equals(prop) ) { + method = pd[i].getWriteMethod(); + type = pd[i].getPropertyType(); + propertyEditorClass = pd[i].getPropertyEditorClass(); + break; + } + } + } + if ( method != null ) { + if (type.isArray()) { + if (request == null) { + throw new JasperException( + Localizer.getMessage("jsp.error.beans.setproperty.noindexset")); + } + Class t = type.getComponentType(); + String[] values = request.getParameterValues(param); + //XXX Please check. + if(values == null) return; + if(t.equals(String.class)) { + method.invoke(bean, new Object[] { values }); + } else { + Object tmpval = null; + createTypedArray (prop, bean, method, values, t, + propertyEditorClass); + } + } else { + if(value == null || (param != null && value.equals(""))) return; + Object oval = convert(prop, value, type, propertyEditorClass); + if ( oval != null ) + method.invoke(bean, new Object[] { oval }); + } + } + } catch (Exception ex) { + throw new JasperException(ex); + } + if (!ignoreMethodNF && (method == null)) { + if (type == null) { + throw new JasperException( + Localizer.getMessage("jsp.error.beans.noproperty", + prop, + bean.getClass().getName())); + } else { + throw new JasperException( + Localizer.getMessage("jsp.error.beans.nomethod.setproperty", + prop, + type.getName(), + bean.getClass().getName())); + } + } + } + // __end introspecthelperMethod + + //------------------------------------------------------------------- + // functions to convert builtin Java data types to string. + //------------------------------------------------------------------- + // __begin toStringMethod + public static String toString(Object o) { + return String.valueOf(o); + } + + public static String toString(byte b) { + return new Byte(b).toString(); + } + + public static String toString(boolean b) { + return new Boolean(b).toString(); + } + + public static String toString(short s) { + return new Short(s).toString(); + } + + public static String toString(int i) { + return new Integer(i).toString(); + } + + public static String toString(float f) { + return new Float(f).toString(); + } + + public static String toString(long l) { + return new Long(l).toString(); + } + + public static String toString(double d) { + return new Double(d).toString(); + } + + public static String toString(char c) { + return new Character(c).toString(); + } + // __end toStringMethod + + + /** + * Create a typed array. + * This is a special case where params are passed through + * the request and the property is indexed. + */ + public static void createTypedArray(String propertyName, + Object bean, + Method method, + String[] values, + Class t, + Class propertyEditorClass) + throws JasperException { + + try { + if (propertyEditorClass != null) { + Object[] tmpval = new Integer[values.length]; + for (int i=0; i^()[]{}$\\\n"; + + for(int index=0; index= encoded.length()) + count = encoded.length(); + else + count += 2; + } else if (cur == '+') { + holdbuffer[bufcount++] = (byte) ' '; + } else { + holdbuffer[bufcount++] = (byte) cur; + } + } + // REVISIT -- remedy for Deprecated warning. + //return new String(holdbuffer,0,0,bufcount); + return new String(holdbuffer,0,bufcount); + } + + // __begin lookupReadMethodMethod + public static Object handleGetProperty(Object o, String prop) + throws JasperException { + if (o == null) { + throw new JasperException( + Localizer.getMessage("jsp.error.beans.nullbean")); + } + Object value = null; + try { + Method method = getReadMethod(o.getClass(), prop); + value = method.invoke(o, (Object[]) null); + } catch (Exception ex) { + throw new JasperException (ex); + } + return value; + } + // __end lookupReadMethodMethod + + // handles with EL expression for 'value' attribute +/** Use proprietaryEvaluate + public static void handleSetPropertyExpression(Object bean, + String prop, String expression, PageContext pageContext, + VariableResolver variableResolver, FunctionMapper functionMapper ) + throws JasperException + { + try { + Method method = getWriteMethod(bean.getClass(), prop); + method.invoke(bean, new Object[] { + pageContext.getExpressionEvaluator().evaluate( + expression, + method.getParameterTypes()[0], + variableResolver, + functionMapper, + null ) + }); + } catch (Exception ex) { + throw new JasperException(ex); + } + } +**/ + public static void handleSetPropertyExpression(Object bean, + String prop, String expression, PageContext pageContext, + ProtectedFunctionMapper functionMapper ) + throws JasperException + { + try { + Method method = getWriteMethod(bean.getClass(), prop); + method.invoke(bean, new Object[] { + PageContextImpl.proprietaryEvaluate( + expression, + method.getParameterTypes()[0], + pageContext, + functionMapper, + false ) + }); + } catch (Exception ex) { + throw new JasperException(ex); + } + } + + public static void handleSetProperty(Object bean, String prop, + Object value) + throws JasperException + { + try { + Method method = getWriteMethod(bean.getClass(), prop); + method.invoke(bean, new Object[] { value }); + } catch (Exception ex) { + throw new JasperException(ex); + } + } + + public static void handleSetProperty(Object bean, String prop, + int value) + throws JasperException + { + try { + Method method = getWriteMethod(bean.getClass(), prop); + method.invoke(bean, new Object[] { new Integer(value) }); + } catch (Exception ex) { + throw new JasperException(ex); + } + } + + public static void handleSetProperty(Object bean, String prop, + short value) + throws JasperException + { + try { + Method method = getWriteMethod(bean.getClass(), prop); + method.invoke(bean, new Object[] { new Short(value) }); + } catch (Exception ex) { + throw new JasperException(ex); + } + } + + public static void handleSetProperty(Object bean, String prop, + long value) + throws JasperException + { + try { + Method method = getWriteMethod(bean.getClass(), prop); + method.invoke(bean, new Object[] { new Long(value) }); + } catch (Exception ex) { + throw new JasperException(ex); + } + } + + public static void handleSetProperty(Object bean, String prop, + double value) + throws JasperException + { + try { + Method method = getWriteMethod(bean.getClass(), prop); + method.invoke(bean, new Object[] { new Double(value) }); + } catch (Exception ex) { + throw new JasperException(ex); + } + } + + public static void handleSetProperty(Object bean, String prop, + float value) + throws JasperException + { + try { + Method method = getWriteMethod(bean.getClass(), prop); + method.invoke(bean, new Object[] { new Float(value) }); + } catch (Exception ex) { + throw new JasperException(ex); + } + } + + public static void handleSetProperty(Object bean, String prop, + char value) + throws JasperException + { + try { + Method method = getWriteMethod(bean.getClass(), prop); + method.invoke(bean, new Object[] { new Character(value) }); + } catch (Exception ex) { + throw new JasperException(ex); + } + } + + public static void handleSetProperty(Object bean, String prop, + byte value) + throws JasperException + { + try { + Method method = getWriteMethod(bean.getClass(), prop); + method.invoke(bean, new Object[] { new Byte(value) }); + } catch (Exception ex) { + throw new JasperException(ex); + } + } + + public static void handleSetProperty(Object bean, String prop, + boolean value) + throws JasperException + { + try { + Method method = getWriteMethod(bean.getClass(), prop); + method.invoke(bean, new Object[] { new Boolean(value) }); + } catch (Exception ex) { + throw new JasperException(ex); + } + } + + public static Method getWriteMethod(Class beanClass, String prop) + throws JasperException { + Method method = null; + Class type = null; + try { + java.beans.BeanInfo info + = java.beans.Introspector.getBeanInfo(beanClass); + if ( info != null ) { + java.beans.PropertyDescriptor pd[] + = info.getPropertyDescriptors(); + for (int i = 0 ; i < pd.length ; i++) { + if ( pd[i].getName().equals(prop) ) { + method = pd[i].getWriteMethod(); + type = pd[i].getPropertyType(); + break; + } + } + } else { + // just in case introspection silently fails. + throw new JasperException( + Localizer.getMessage("jsp.error.beans.nobeaninfo", + beanClass.getName())); + } + } catch (Exception ex) { + throw new JasperException (ex); + } + if (method == null) { + if (type == null) { + throw new JasperException( + Localizer.getMessage("jsp.error.beans.noproperty", + prop, + beanClass.getName())); + } else { + throw new JasperException( + Localizer.getMessage("jsp.error.beans.nomethod.setproperty", + prop, + type.getName(), + beanClass.getName())); + } + } + return method; + } + + public static Method getReadMethod(Class beanClass, String prop) + throws JasperException { + + Method method = null; + Class type = null; + try { + java.beans.BeanInfo info + = java.beans.Introspector.getBeanInfo(beanClass); + if ( info != null ) { + java.beans.PropertyDescriptor pd[] + = info.getPropertyDescriptors(); + for (int i = 0 ; i < pd.length ; i++) { + if ( pd[i].getName().equals(prop) ) { + method = pd[i].getReadMethod(); + type = pd[i].getPropertyType(); + break; + } + } + } else { + // just in case introspection silently fails. + throw new JasperException( + Localizer.getMessage("jsp.error.beans.nobeaninfo", + beanClass.getName())); + } + } catch (Exception ex) { + throw new JasperException (ex); + } + if (method == null) { + if (type == null) { + throw new JasperException( + Localizer.getMessage("jsp.error.beans.noproperty", prop, + beanClass.getName())); + } else { + throw new JasperException( + Localizer.getMessage("jsp.error.beans.nomethod", prop, + beanClass.getName())); + } + } + + return method; + } + + //********************************************************************* + // PropertyEditor Support + + public static Object getValueFromBeanInfoPropertyEditor( + Class attrClass, String attrName, String attrValue, + Class propertyEditorClass) + throws JasperException + { + try { + PropertyEditor pe = (PropertyEditor)propertyEditorClass.newInstance(); + pe.setAsText(attrValue); + return pe.getValue(); + } catch (Exception ex) { + throw new JasperException( + Localizer.getMessage("jsp.error.beans.property.conversion", + attrValue, attrClass.getName(), attrName, + ex.getMessage())); + } + } + + public static Object getValueFromPropertyEditorManager( + Class attrClass, String attrName, String attrValue) + throws JasperException + { + try { + PropertyEditor propEditor = + PropertyEditorManager.findEditor(attrClass); + if (propEditor != null) { + propEditor.setAsText(attrValue); + return propEditor.getValue(); + } else { + throw new IllegalArgumentException( + Localizer.getMessage("jsp.error.beans.propertyeditor.notregistered")); + } + } catch (IllegalArgumentException ex) { + throw new JasperException( + Localizer.getMessage("jsp.error.beans.property.conversion", + attrValue, attrClass.getName(), attrName, + ex.getMessage())); + } + } + + + // ************************************************************************ + // General Purpose Runtime Methods + // ************************************************************************ + + + /** + * Convert a possibly relative resource path into a context-relative + * resource path that starts with a '/'. + * + * @param request The servlet request we are processing + * @param relativePath The possibly relative resource path + */ + public static String getContextRelativePath(ServletRequest request, + String relativePath) { + + if (relativePath.startsWith("/")) + return (relativePath); + if (!(request instanceof HttpServletRequest)) + return (relativePath); + HttpServletRequest hrequest = (HttpServletRequest) request; + String uri = (String) + request.getAttribute("javax.servlet.include.servlet_path"); + if (uri != null) { + String pathInfo = (String) + request.getAttribute("javax.servlet.include.path_info"); + if (pathInfo == null) { + if (uri.lastIndexOf('/') >= 0) + uri = uri.substring(0, uri.lastIndexOf('/')); + } + } + else { + uri = hrequest.getServletPath(); + if (uri.lastIndexOf('/') >= 0) + uri = uri.substring(0, uri.lastIndexOf('/')); + } + return uri + '/' + relativePath; + + } + + + /** + * Perform a RequestDispatcher.include() operation, with optional flushing + * of the response beforehand. + * + * @param request The servlet request we are processing + * @param response The servlet response we are processing + * @param relativePath The relative path of the resource to be included + * @param out The Writer to whom we are currently writing + * @param flush Should we flush before the include is processed? + * + * @exception IOException if thrown by the included servlet + * @exception ServletException if thrown by the included servlet + */ + public static void include(ServletRequest request, + ServletResponse response, + String relativePath, + JspWriter out, + boolean flush) + throws IOException, ServletException { + + if (flush && !(out instanceof BodyContent)) + out.flush(); + + // FIXME - It is tempting to use request.getRequestDispatcher() to + // resolve a relative path directly, but Catalina currently does not + // take into account whether the caller is inside a RequestDispatcher + // include or not. Whether Catalina *should* take that into account + // is a spec issue currently under review. In the mean time, + // replicate Jasper's previous behavior + + String resourcePath = getContextRelativePath(request, relativePath); + RequestDispatcher rd = request.getRequestDispatcher(resourcePath); + + rd.include(request, + new ServletResponseWrapperInclude(response, out)); + + } + + /** + * URL encodes a string, based on the supplied character encoding. + * This performs the same function as java.next.URLEncode.encode + * in J2SDK1.4, and should be removed if the only platform supported + * is 1.4 or higher. + * @param s The String to be URL encoded. + * @param enc The character encoding + * @return The URL encoded String + */ + public static String URLEncode(String s, String enc) { + + if (s == null) { + return "null"; + } + + if (enc == null) { + enc = "ISO-8859-1"; // The default request encoding + } + + StringBuffer out = new StringBuffer(s.length()); + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + OutputStreamWriter writer = null; + try { + writer = new OutputStreamWriter(buf, enc); + } catch (java.io.UnsupportedEncodingException ex) { + // Use the default encoding? + writer = new OutputStreamWriter(buf); + } + + for (int i = 0; i < s.length(); i++) { + int c = s.charAt(i); + if (c == ' ') { + out.append('+'); + } else if (isSafeChar(c)) { + out.append((char)c); + } else { + // convert to external encoding before hex conversion + try { + writer.write(c); + writer.flush(); + } catch(IOException e) { + buf.reset(); + continue; + } + byte[] ba = buf.toByteArray(); + for (int j = 0; j < ba.length; j++) { + out.append('%'); + // Converting each byte in the buffer + out.append(Character.forDigit((ba[j]>>4) & 0xf, 16)); + out.append(Character.forDigit(ba[j] & 0xf, 16)); + } + buf.reset(); + } + } + return out.toString(); + } + + private static boolean isSafeChar(int c) { + if (c >= 'a' && c <= 'z') { + return true; + } + if (c >= 'A' && c <= 'Z') { + return true; + } + if (c >= '0' && c <= '9') { + return true; + } + if (c == '-' || c == '_' || c == '.' || c == '!' || + c == '~' || c == '*' || c == '\'' || c == '(' || c == ')') { + return true; + } + return false; + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspSourceDependent.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspSourceDependent.java new file mode 100644 index 000000000..2ba9364a6 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspSourceDependent.java @@ -0,0 +1,39 @@ +/* + * 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.runtime; + +/** + * Interface for tracking the source files dependencies, for the purpose + * of compiling out of date pages. This is used for + * 1) files that are included by page directives + * 2) files that are included by include-prelude and include-coda in jsp:config + * 3) files that are tag files and referenced + * 4) TLDs referenced + */ + +public interface JspSourceDependent { + + /** + * Returns a list of files names that the current page has a source + * dependency on. + */ + // FIXME: Type used is Object due to very weird behavior + // with Eclipse JDT 3.1 in Java 5 mode + public Object getDependants(); + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspWriterImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspWriterImpl.java new file mode 100644 index 000000000..cf7a44383 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspWriterImpl.java @@ -0,0 +1,590 @@ +/* + * 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.runtime; + +import java.io.IOException; +import java.io.Writer; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import javax.servlet.ServletResponse; +import javax.servlet.jsp.JspWriter; + +import org.apache.jasper.Constants; +import org.apache.jasper.compiler.Localizer; +import org.apache.jasper.security.SecurityUtil; + +/** + * Write text to a character-output stream, buffering characters so as + * to provide for the efficient writing of single characters, arrays, + * and strings. + * + * Provide support for discarding for the output that has been + * buffered. + * + * This needs revisiting when the buffering problems in the JSP spec + * are fixed -akv + * + * @author Anil K. Vijendran + */ +public class JspWriterImpl extends JspWriter { + + private Writer out; + private ServletResponse response; + private char cb[]; + private int nextChar; + private boolean flushed = false; + private boolean closed = false; + + public JspWriterImpl() { + super( Constants.DEFAULT_BUFFER_SIZE, true ); + } + + /** + * Create a buffered character-output stream that uses a default-sized + * output buffer. + * + * @param response A Servlet Response + */ + public JspWriterImpl(ServletResponse response) { + this(response, Constants.DEFAULT_BUFFER_SIZE, true); + } + + /** + * Create a new buffered character-output stream that uses an output + * buffer of the given size. + * + * @param response A Servlet Response + * @param sz Output-buffer size, a positive integer + * + * @exception IllegalArgumentException If sz is <= 0 + */ + public JspWriterImpl(ServletResponse response, int sz, + boolean autoFlush) { + super(sz, autoFlush); + if (sz < 0) + throw new IllegalArgumentException("Buffer size <= 0"); + this.response = response; + cb = sz == 0 ? null : new char[sz]; + nextChar = 0; + } + + void init( ServletResponse response, int sz, boolean autoFlush ) { + this.response= response; + if( sz > 0 && ( cb == null || sz > cb.length ) ) + cb=new char[sz]; + nextChar = 0; + this.autoFlush=autoFlush; + this.bufferSize=sz; + } + + /** Package-level access + */ + void recycle() { + flushed = false; + closed = false; + out = null; + nextChar = 0; + response = null; + } + + /** + * Flush the output buffer to the underlying character stream, without + * flushing the stream itself. This method is non-private only so that it + * may be invoked by PrintStream. + */ + protected final void flushBuffer() throws IOException { + if (bufferSize == 0) + return; + flushed = true; + ensureOpen(); + if (nextChar == 0) + return; + initOut(); + out.write(cb, 0, nextChar); + nextChar = 0; + } + + private void initOut() throws IOException { + if (out == null) { + out = response.getWriter(); + } + } + + private String getLocalizeMessage(final String message){ + if (SecurityUtil.isPackageProtectionEnabled()){ + return (String)AccessController.doPrivileged(new PrivilegedAction(){ + public Object run(){ + return Localizer.getMessage(message); + } + }); + } else { + return Localizer.getMessage(message); + } + } + + /** + * Discard the output buffer. + */ + public final void clear() throws IOException { + if ((bufferSize == 0) && (out != null)) + // clear() is illegal after any unbuffered output (JSP.5.5) + throw new IllegalStateException( + getLocalizeMessage("jsp.error.ise_on_clear")); + if (flushed) + throw new IOException( + getLocalizeMessage("jsp.error.attempt_to_clear_flushed_buffer")); + ensureOpen(); + nextChar = 0; + } + + public void clearBuffer() throws IOException { + if (bufferSize == 0) + throw new IllegalStateException( + getLocalizeMessage("jsp.error.ise_on_clear")); + ensureOpen(); + nextChar = 0; + } + + private final void bufferOverflow() throws IOException { + throw new IOException(getLocalizeMessage("jsp.error.overflow")); + } + + /** + * Flush the stream. + * + */ + public void flush() throws IOException { + flushBuffer(); + if (out != null) { + out.flush(); + } + } + + /** + * Close the stream. + * + */ + public void close() throws IOException { + if (response == null || closed) + // multiple calls to close is OK + return; + flush(); + if (out != null) + out.close(); + out = null; + closed = true; + } + + /** + * @return the number of bytes unused in the buffer + */ + public int getRemaining() { + return bufferSize - nextChar; + } + + /** check to make sure that the stream has not been closed */ + private void ensureOpen() throws IOException { + if (response == null || closed) + throw new IOException("Stream closed"); + } + + + /** + * Write a single character. + */ + public void write(int c) throws IOException { + ensureOpen(); + if (bufferSize == 0) { + initOut(); + out.write(c); + } + else { + if (nextChar >= bufferSize) + if (autoFlush) + flushBuffer(); + else + bufferOverflow(); + cb[nextChar++] = (char) c; + } + } + + /** + * Our own little min method, to avoid loading java.lang.Math if we've run + * out of file descriptors and we're trying to print a stack trace. + */ + private int min(int a, int b) { + if (a < b) return a; + return b; + } + + /** + * Write a portion of an array of characters. + * + *

Ordinarily this method stores characters from the given array into + * this stream's buffer, flushing the buffer to the underlying stream as + * needed. If the requested length is at least as large as the buffer, + * however, then this method will flush the buffer and write the characters + * directly to the underlying stream. Thus redundant + * DiscardableBufferedWriters will not copy data unnecessarily. + * + * @param cbuf A character array + * @param off Offset from which to start reading characters + * @param len Number of characters to write + */ + public void write(char cbuf[], int off, int len) + throws IOException + { + ensureOpen(); + + if (bufferSize == 0) { + initOut(); + out.write(cbuf, off, len); + return; + } + + if ((off < 0) || (off > cbuf.length) || (len < 0) || + ((off + len) > cbuf.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + + if (len >= bufferSize) { + /* If the request length exceeds the size of the output buffer, + flush the buffer and then write the data directly. In this + way buffered streams will cascade harmlessly. */ + if (autoFlush) + flushBuffer(); + else + bufferOverflow(); + initOut(); + out.write(cbuf, off, len); + return; + } + + int b = off, t = off + len; + while (b < t) { + int d = min(bufferSize - nextChar, t - b); + System.arraycopy(cbuf, b, cb, nextChar, d); + b += d; + nextChar += d; + if (nextChar >= bufferSize) + if (autoFlush) + flushBuffer(); + else + bufferOverflow(); + } + + } + + /** + * Write an array of characters. This method cannot be inherited from the + * Writer class because it must suppress I/O exceptions. + */ + public void write(char buf[]) throws IOException { + write(buf, 0, buf.length); + } + + /** + * Write a portion of a String. + * + * @param s String to be written + * @param off Offset from which to start reading characters + * @param len Number of characters to be written + */ + public void write(String s, int off, int len) throws IOException { + ensureOpen(); + if (bufferSize == 0) { + initOut(); + out.write(s, off, len); + return; + } + int b = off, t = off + len; + while (b < t) { + int d = min(bufferSize - nextChar, t - b); + s.getChars(b, b + d, cb, nextChar); + b += d; + nextChar += d; + if (nextChar >= bufferSize) + if (autoFlush) + flushBuffer(); + else + bufferOverflow(); + } + } + + /** + * Write a string. This method cannot be inherited from the Writer class + * because it must suppress I/O exceptions. + */ + public void write(String s) throws IOException { + // Simple fix for Bugzilla 35410 + // Calling the other write function so as to init the buffer anyways + if(s == null) { + write(s, 0, 0); + } else { + write(s, 0, s.length()); + } + } + + + static String lineSeparator = System.getProperty("line.separator"); + + /** + * Write a line separator. The line separator string is defined by the + * system property line.separator, and is not necessarily a single + * newline ('\n') character. + * + * @exception IOException If an I/O error occurs + */ + + public void newLine() throws IOException { + write(lineSeparator); + } + + + /* Methods that do not terminate lines */ + + /** + * Print a boolean value. The string produced by {@link + * java.lang.String#valueOf(boolean)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link + * #write(int)} method. + * + * @param b The boolean to be printed + */ + public void print(boolean b) throws IOException { + write(b ? "true" : "false"); + } + + /** + * Print a character. The character is translated into one or more bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link + * #write(int)} method. + * + * @param c The char to be printed + */ + public void print(char c) throws IOException { + write(String.valueOf(c)); + } + + /** + * Print an integer. The string produced by {@link + * java.lang.String#valueOf(int)} is translated into bytes according + * to the platform's default character encoding, and these bytes are + * written in exactly the manner of the {@link #write(int)} + * method. + * + * @param i The int to be printed + */ + public void print(int i) throws IOException { + write(String.valueOf(i)); + } + + /** + * Print a long integer. The string produced by {@link + * java.lang.String#valueOf(long)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link #write(int)} + * method. + * + * @param l The long to be printed + */ + public void print(long l) throws IOException { + write(String.valueOf(l)); + } + + /** + * Print a floating-point number. The string produced by {@link + * java.lang.String#valueOf(float)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link #write(int)} + * method. + * + * @param f The float to be printed + */ + public void print(float f) throws IOException { + write(String.valueOf(f)); + } + + /** + * Print a double-precision floating-point number. The string produced by + * {@link java.lang.String#valueOf(double)} is translated into + * bytes according to the platform's default character encoding, and these + * bytes are written in exactly the manner of the {@link + * #write(int)} method. + * + * @param d The double to be printed + */ + public void print(double d) throws IOException { + write(String.valueOf(d)); + } + + /** + * Print an array of characters. The characters are converted into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link #write(int)} + * method. + * + * @param s The array of chars to be printed + * + * @throws NullPointerException If s is null + */ + public void print(char s[]) throws IOException { + write(s); + } + + /** + * Print a string. If the argument is null then the string + * "null" is printed. Otherwise, the string's characters are + * converted into bytes according to the platform's default character + * encoding, and these bytes are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param s The String to be printed + */ + public void print(String s) throws IOException { + if (s == null) { + s = "null"; + } + write(s); + } + + /** + * Print an object. The string produced by the {@link + * java.lang.String#valueOf(Object)} method is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link #write(int)} + * method. + * + * @param obj The Object to be printed + */ + public void print(Object obj) throws IOException { + write(String.valueOf(obj)); + } + + /* Methods that do terminate lines */ + + /** + * Terminate the current line by writing the line separator string. The + * line separator string is defined by the system property + * line.separator, and is not necessarily a single newline + * character ('\n'). + * + * Need to change this from PrintWriter because the default + * println() writes to the sink directly instead of through the + * write method... + */ + public void println() throws IOException { + newLine(); + } + + /** + * Print a boolean value and then terminate the line. This method behaves + * as though it invokes {@link #print(boolean)} and then + * {@link #println()}. + */ + public void println(boolean x) throws IOException { + print(x); + println(); + } + + /** + * Print a character and then terminate the line. This method behaves as + * though it invokes {@link #print(char)} and then {@link + * #println()}. + */ + public void println(char x) throws IOException { + print(x); + println(); + } + + /** + * Print an integer and then terminate the line. This method behaves as + * though it invokes {@link #print(int)} and then {@link + * #println()}. + */ + public void println(int x) throws IOException { + print(x); + println(); + } + + /** + * Print a long integer and then terminate the line. This method behaves + * as though it invokes {@link #print(long)} and then + * {@link #println()}. + */ + public void println(long x) throws IOException { + print(x); + println(); + } + + /** + * Print a floating-point number and then terminate the line. This method + * behaves as though it invokes {@link #print(float)} and then + * {@link #println()}. + */ + public void println(float x) throws IOException { + print(x); + println(); + } + + /** + * Print a double-precision floating-point number and then terminate the + * line. This method behaves as though it invokes {@link + * #print(double)} and then {@link #println()}. + */ + public void println(double x) throws IOException { + print(x); + println(); + } + + /** + * Print an array of characters and then terminate the line. This method + * behaves as though it invokes {@link #print(char[])} and then + * {@link #println()}. + */ + public void println(char x[]) throws IOException { + print(x); + println(); + } + + /** + * Print a String and then terminate the line. This method behaves as + * though it invokes {@link #print(String)} and then + * {@link #println()}. + */ + public void println(String x) throws IOException { + print(x); + println(); + } + + /** + * Print an Object and then terminate the line. This method behaves as + * though it invokes {@link #print(Object)} and then + * {@link #println()}. + */ + public void println(Object x) throws IOException { + print(x); + println(); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/PageContextImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/PageContextImpl.java new file mode 100644 index 000000000..4b71e8a37 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/PageContextImpl.java @@ -0,0 +1,951 @@ +/* + * 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.runtime; + +import java.io.IOException; +import java.io.Writer; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Enumeration; +import java.util.HashMap; + +import javax.el.ELContext; +import javax.el.ExpressionFactory; +import javax.el.ValueExpression; +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.JspFactory; +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.PageContext; +import javax.servlet.jsp.el.ELException; +import javax.servlet.jsp.el.ExpressionEvaluator; +import javax.servlet.jsp.el.VariableResolver; +import javax.servlet.jsp.tagext.BodyContent; + +import org.apache.jasper.Constants; +import org.apache.jasper.compiler.Localizer; +import org.apache.jasper.el.ELContextImpl; +import org.apache.jasper.el.ExpressionEvaluatorImpl; +import org.apache.jasper.el.FunctionMapperImpl; +import org.apache.jasper.el.VariableResolverImpl; +import org.apache.jasper.security.SecurityUtil; +import org.apache.jasper.util.Enumerator; + +/** + * Implementation of the PageContext class from the JSP spec. Also doubles as a + * VariableResolver for the EL. + * + * @author Anil K. Vijendran + * @author Larry Cable + * @author Hans Bergsten + * @author Pierre Delisle + * @author Mark Roth + * @author Jan Luehe + * @author Jacob Hookom + */ +public class PageContextImpl extends PageContext { + + private static final JspFactory jspf = JspFactory.getDefaultFactory(); + + private BodyContentImpl[] outs; + + private int depth; + + // per-servlet state + private Servlet servlet; + + private ServletConfig config; + + private ServletContext context; + + private JspApplicationContextImpl applicationContext; + + private String errorPageURL; + + // page-scope attributes + private transient HashMap attributes; + + // per-request state + private transient ServletRequest request; + + private transient ServletResponse response; + + private transient HttpSession session; + + private transient ELContextImpl elContext; + + private boolean isIncluded; + + + // initial output stream + private transient JspWriter out; + + private transient JspWriterImpl baseOut; + + /* + * Constructor. + */ + PageContextImpl() { + this.outs = new BodyContentImpl[0]; + this.attributes = new HashMap(16); + this.depth = -1; + } + + public void initialize(Servlet servlet, ServletRequest request, + ServletResponse response, String errorPageURL, + boolean needsSession, int bufferSize, boolean autoFlush) + throws IOException { + + _initialize(servlet, request, response, errorPageURL, needsSession, + bufferSize, autoFlush); + } + + private void _initialize(Servlet servlet, ServletRequest request, + ServletResponse response, String errorPageURL, + boolean needsSession, int bufferSize, boolean autoFlush) + throws IOException { + + // initialize state + this.servlet = servlet; + this.config = servlet.getServletConfig(); + this.context = config.getServletContext(); + this.errorPageURL = errorPageURL; + this.request = request; + this.response = response; + + // initialize application context + this.applicationContext = JspApplicationContextImpl.getInstance(context); + + // Setup session (if required) + if (request instanceof HttpServletRequest && needsSession) + this.session = ((HttpServletRequest) request).getSession(); + if (needsSession && session == null) + throw new IllegalStateException( + "Page needs a session and none is available"); + + // initialize the initial out ... + depth = -1; + if (this.baseOut == null) { + this.baseOut = new JspWriterImpl(response, bufferSize, autoFlush); + } else { + this.baseOut.init(response, bufferSize, autoFlush); + } + this.out = baseOut; + + // register names/values as per spec + setAttribute(OUT, this.out); + setAttribute(REQUEST, request); + setAttribute(RESPONSE, response); + + if (session != null) + setAttribute(SESSION, session); + + setAttribute(PAGE, servlet); + setAttribute(CONFIG, config); + setAttribute(PAGECONTEXT, this); + setAttribute(APPLICATION, context); + + isIncluded = request.getAttribute("javax.servlet.include.servlet_path") != null; + } + + public void release() { + out = baseOut; + try { + if (isIncluded) { + ((JspWriterImpl) out).flushBuffer(); + // push it into the including jspWriter + } else { + // Old code: + // out.flush(); + // Do not flush the buffer even if we're not included (i.e. + // we are the main page. The servlet will flush it and close + // the stream. + ((JspWriterImpl) out).flushBuffer(); + } + } catch (IOException ex) { + IllegalStateException ise = new IllegalStateException(Localizer.getMessage("jsp.error.flush"), ex); + throw ise; + } finally { + servlet = null; + config = null; + context = null; + applicationContext = null; + elContext = null; + errorPageURL = null; + request = null; + response = null; + depth = -1; + baseOut.recycle(); + session = null; + attributes.clear(); + } + } + + public Object getAttribute(final String name) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + if (SecurityUtil.isPackageProtectionEnabled()) { + return AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return doGetAttribute(name); + } + }); + } else { + return doGetAttribute(name); + } + + } + + private Object doGetAttribute(String name) { + return attributes.get(name); + } + + public Object getAttribute(final String name, final int scope) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + if (SecurityUtil.isPackageProtectionEnabled()) { + return AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return doGetAttribute(name, scope); + } + }); + } else { + return doGetAttribute(name, scope); + } + + } + + private Object doGetAttribute(String name, int scope) { + switch (scope) { + case PAGE_SCOPE: + return attributes.get(name); + + case REQUEST_SCOPE: + return request.getAttribute(name); + + case SESSION_SCOPE: + if (session == null) { + throw new IllegalStateException(Localizer + .getMessage("jsp.error.page.noSession")); + } + return session.getAttribute(name); + + case APPLICATION_SCOPE: + return context.getAttribute(name); + + default: + throw new IllegalArgumentException("Invalid scope"); + } + } + + public void setAttribute(final String name, final Object attribute) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + if (SecurityUtil.isPackageProtectionEnabled()) { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + doSetAttribute(name, attribute); + return null; + } + }); + } else { + doSetAttribute(name, attribute); + } + } + + private void doSetAttribute(String name, Object attribute) { + if (attribute != null) { + attributes.put(name, attribute); + } else { + removeAttribute(name, PAGE_SCOPE); + } + } + + public void setAttribute(final String name, final Object o, final int scope) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + if (SecurityUtil.isPackageProtectionEnabled()) { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + doSetAttribute(name, o, scope); + return null; + } + }); + } else { + doSetAttribute(name, o, scope); + } + + } + + private void doSetAttribute(String name, Object o, int scope) { + if (o != null) { + switch (scope) { + case PAGE_SCOPE: + attributes.put(name, o); + break; + + case REQUEST_SCOPE: + request.setAttribute(name, o); + break; + + case SESSION_SCOPE: + if (session == null) { + throw new IllegalStateException(Localizer + .getMessage("jsp.error.page.noSession")); + } + session.setAttribute(name, o); + break; + + case APPLICATION_SCOPE: + context.setAttribute(name, o); + break; + + default: + throw new IllegalArgumentException("Invalid scope"); + } + } else { + removeAttribute(name, scope); + } + } + + public void removeAttribute(final String name, final int scope) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + if (SecurityUtil.isPackageProtectionEnabled()) { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + doRemoveAttribute(name, scope); + return null; + } + }); + } else { + doRemoveAttribute(name, scope); + } + } + + private void doRemoveAttribute(String name, int scope) { + switch (scope) { + case PAGE_SCOPE: + attributes.remove(name); + break; + + case REQUEST_SCOPE: + request.removeAttribute(name); + break; + + case SESSION_SCOPE: + if (session == null) { + throw new IllegalStateException(Localizer + .getMessage("jsp.error.page.noSession")); + } + session.removeAttribute(name); + break; + + case APPLICATION_SCOPE: + context.removeAttribute(name); + break; + + default: + throw new IllegalArgumentException("Invalid scope"); + } + } + + public int getAttributesScope(final String name) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + if (SecurityUtil.isPackageProtectionEnabled()) { + return ((Integer) AccessController + .doPrivileged(new PrivilegedAction() { + public Object run() { + return new Integer(doGetAttributeScope(name)); + } + })).intValue(); + } else { + return doGetAttributeScope(name); + } + } + + private int doGetAttributeScope(String name) { + if (attributes.get(name) != null) + return PAGE_SCOPE; + + if (request.getAttribute(name) != null) + return REQUEST_SCOPE; + + if (session != null) { + try { + if (session.getAttribute(name) != null) + return SESSION_SCOPE; + } catch(IllegalStateException ise) { + // Session has been invalidated. + // Ignore and fall through to application scope. + } + } + + if (context.getAttribute(name) != null) + return APPLICATION_SCOPE; + + return 0; + } + + public Object findAttribute(final String name) { + if (SecurityUtil.isPackageProtectionEnabled()) { + return AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + return doFindAttribute(name); + } + }); + } else { + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + return doFindAttribute(name); + } + } + + private Object doFindAttribute(String name) { + + Object o = attributes.get(name); + if (o != null) + return o; + + o = request.getAttribute(name); + if (o != null) + return o; + + if (session != null) { + try { + o = session.getAttribute(name); + } catch(IllegalStateException ise) { + // Session has been invalidated. + // Ignore and fall through to application scope. + } + if (o != null) + return o; + } + + return context.getAttribute(name); + } + + public Enumeration getAttributeNamesInScope(final int scope) { + if (SecurityUtil.isPackageProtectionEnabled()) { + return (Enumeration) AccessController + .doPrivileged(new PrivilegedAction() { + public Object run() { + return doGetAttributeNamesInScope(scope); + } + }); + } else { + return doGetAttributeNamesInScope(scope); + } + } + + private Enumeration doGetAttributeNamesInScope(int scope) { + switch (scope) { + case PAGE_SCOPE: + return new Enumerator(attributes.keySet().iterator()); + + case REQUEST_SCOPE: + return request.getAttributeNames(); + + case SESSION_SCOPE: + if (session == null) { + throw new IllegalStateException(Localizer + .getMessage("jsp.error.page.noSession")); + } + return session.getAttributeNames(); + + case APPLICATION_SCOPE: + return context.getAttributeNames(); + + default: + throw new IllegalArgumentException("Invalid scope"); + } + } + + public void removeAttribute(final String name) { + + if (name == null) { + throw new NullPointerException(Localizer + .getMessage("jsp.error.attribute.null_name")); + } + + if (SecurityUtil.isPackageProtectionEnabled()) { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + doRemoveAttribute(name); + return null; + } + }); + } else { + doRemoveAttribute(name); + } + } + + private void doRemoveAttribute(String name) { + removeAttribute(name, PAGE_SCOPE); + removeAttribute(name, REQUEST_SCOPE); + if( session != null ) { + try { + removeAttribute(name, SESSION_SCOPE); + } catch(IllegalStateException ise) { + // Session has been invalidated. + // Ignore and fall throw to application scope. + } + } + removeAttribute(name, APPLICATION_SCOPE); + } + + public JspWriter getOut() { + return out; + } + + public HttpSession getSession() { + return session; + } + + public Servlet getServlet() { + return servlet; + } + + public ServletConfig getServletConfig() { + return config; + } + + public ServletContext getServletContext() { + return config.getServletContext(); + } + + public ServletRequest getRequest() { + return request; + } + + public ServletResponse getResponse() { + return response; + } + + /** + * Returns the exception associated with this page context, if any.

+ * Added wrapping for Throwables to avoid ClassCastException: see Bugzilla + * 31171 for details. + * + * @return The Exception associated with this page context, if any. + */ + public Exception getException() { + Throwable t = JspRuntimeLibrary.getThrowable(request); + + // Only wrap if needed + if ((t != null) && (!(t instanceof Exception))) { + t = new JspException(t); + } + + return (Exception) t; + } + + public Object getPage() { + return servlet; + } + + private final String getAbsolutePathRelativeToContext(String relativeUrlPath) { + String path = relativeUrlPath; + + if (!path.startsWith("/")) { + String uri = (String) request + .getAttribute("javax.servlet.include.servlet_path"); + if (uri == null) + uri = ((HttpServletRequest) request).getServletPath(); + String baseURI = uri.substring(0, uri.lastIndexOf('/')); + path = baseURI + '/' + path; + } + + return path; + } + + public void include(String relativeUrlPath) throws ServletException, + IOException { + JspRuntimeLibrary + .include(request, response, relativeUrlPath, out, true); + } + + public void include(final String relativeUrlPath, final boolean flush) + throws ServletException, IOException { + if (SecurityUtil.isPackageProtectionEnabled()) { + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + doInclude(relativeUrlPath, flush); + return null; + } + }); + } catch (PrivilegedActionException e) { + Exception ex = e.getException(); + if (ex instanceof IOException) { + throw (IOException) ex; + } else { + throw (ServletException) ex; + } + } + } else { + doInclude(relativeUrlPath, flush); + } + } + + private void doInclude(String relativeUrlPath, boolean flush) + throws ServletException, IOException { + JspRuntimeLibrary.include(request, response, relativeUrlPath, out, + flush); + } + + public VariableResolver getVariableResolver() { + return new VariableResolverImpl(this.getELContext()); + } + + public void forward(final String relativeUrlPath) throws ServletException, + IOException { + if (SecurityUtil.isPackageProtectionEnabled()) { + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + doForward(relativeUrlPath); + return null; + } + }); + } catch (PrivilegedActionException e) { + Exception ex = e.getException(); + if (ex instanceof IOException) { + throw (IOException) ex; + } else { + throw (ServletException) ex; + } + } + } else { + doForward(relativeUrlPath); + } + } + + private void doForward(String relativeUrlPath) throws ServletException, + IOException { + + // JSP.4.5 If the buffer was flushed, throw IllegalStateException + try { + out.clear(); + } catch (IOException ex) { + IllegalStateException ise = new IllegalStateException(Localizer + .getMessage("jsp.error.attempt_to_clear_flushed_buffer")); + ise.initCause(ex); + throw ise; + } + + // Make sure that the response object is not the wrapper for include + while (response instanceof ServletResponseWrapperInclude) { + response = ((ServletResponseWrapperInclude) response).getResponse(); + } + + final String path = getAbsolutePathRelativeToContext(relativeUrlPath); + String includeUri = (String) request + .getAttribute(Constants.INC_SERVLET_PATH); + + if (includeUri != null) + request.removeAttribute(Constants.INC_SERVLET_PATH); + try { + context.getRequestDispatcher(path).forward(request, response); + } finally { + if (includeUri != null) + request.setAttribute(Constants.INC_SERVLET_PATH, includeUri); + } + } + + public BodyContent pushBody() { + return (BodyContent) pushBody(null); + } + + public JspWriter pushBody(Writer writer) { + depth++; + if (depth >= outs.length) { + BodyContentImpl[] newOuts = new BodyContentImpl[depth + 1]; + for (int i = 0; i < outs.length; i++) { + newOuts[i] = outs[i]; + } + newOuts[depth] = new BodyContentImpl(out); + outs = newOuts; + } + + outs[depth].setWriter(writer); + out = outs[depth]; + + // Update the value of the "out" attribute in the page scope + // attribute namespace of this PageContext + setAttribute(OUT, out); + + return outs[depth]; + } + + public JspWriter popBody() { + depth--; + if (depth >= 0) { + out = outs[depth]; + } else { + out = baseOut; + } + + // Update the value of the "out" attribute in the page scope + // attribute namespace of this PageContext + setAttribute(OUT, out); + + return out; + } + + /** + * Provides programmatic access to the ExpressionEvaluator. The JSP + * Container must return a valid instance of an ExpressionEvaluator that can + * parse EL expressions. + */ + public ExpressionEvaluator getExpressionEvaluator() { + return new ExpressionEvaluatorImpl(this.applicationContext.getExpressionFactory()); + } + + public void handlePageException(Exception ex) throws IOException, + ServletException { + // Should never be called since handleException() called with a + // Throwable in the generated servlet. + handlePageException((Throwable) ex); + } + + public void handlePageException(final Throwable t) throws IOException, + ServletException { + if (t == null) + throw new NullPointerException("null Throwable"); + + if (SecurityUtil.isPackageProtectionEnabled()) { + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + doHandlePageException(t); + return null; + } + }); + } catch (PrivilegedActionException e) { + Exception ex = e.getException(); + if (ex instanceof IOException) { + throw (IOException) ex; + } else { + throw (ServletException) ex; + } + } + } else { + doHandlePageException(t); + } + + } + + private void doHandlePageException(Throwable t) throws IOException, + ServletException { + + if (errorPageURL != null && !errorPageURL.equals("")) { + + /* + * Set request attributes. Do not set the + * javax.servlet.error.exception attribute here (instead, set in the + * generated servlet code for the error page) in order to prevent + * the ErrorReportValve, which is invoked as part of forwarding the + * request to the error page, from throwing it if the response has + * not been committed (the response will have been committed if the + * error page is a JSP page). + */ + request.setAttribute("javax.servlet.jsp.jspException", t); + request.setAttribute("javax.servlet.error.status_code", + new Integer(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); + request.setAttribute("javax.servlet.error.request_uri", + ((HttpServletRequest) request).getRequestURI()); + request.setAttribute("javax.servlet.error.servlet_name", config + .getServletName()); + try { + forward(errorPageURL); + } catch (IllegalStateException ise) { + include(errorPageURL); + } + + // The error page could be inside an include. + + Object newException = request + .getAttribute("javax.servlet.error.exception"); + + // t==null means the attribute was not set. + if ((newException != null) && (newException == t)) { + request.removeAttribute("javax.servlet.error.exception"); + } + + // now clear the error code - to prevent double handling. + request.removeAttribute("javax.servlet.error.status_code"); + request.removeAttribute("javax.servlet.error.request_uri"); + request.removeAttribute("javax.servlet.error.status_code"); + request.removeAttribute("javax.servlet.jsp.jspException"); + + } else { + // Otherwise throw the exception wrapped inside a ServletException. + // Set the exception as the root cause in the ServletException + // to get a stack trace for the real problem + if (t instanceof IOException) + throw (IOException) t; + if (t instanceof ServletException) + throw (ServletException) t; + if (t instanceof RuntimeException) + throw (RuntimeException) t; + + Throwable rootCause = null; + if (t instanceof JspException) { + rootCause = ((JspException) t).getRootCause(); + } else if (t instanceof ELException) { + rootCause = ((ELException) t).getRootCause(); + } + + if (rootCause != null) { + throw new ServletException(t.getClass().getName() + ": " + + t.getMessage(), rootCause); + } + + throw new ServletException(t); + } + } + + private static String XmlEscape(String s) { + if (s == null) + return null; + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == '<') { + sb.append("<"); + } else if (c == '>') { + sb.append(">"); + } else if (c == '\'') { + sb.append("'"); // ' + } else if (c == '&') { + sb.append("&"); + } else if (c == '"') { + sb.append("""); // " + } else { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Proprietary method to evaluate EL expressions. XXX - This method should + * go away once the EL interpreter moves out of JSTL and into its own + * project. For now, this is necessary because the standard machinery is too + * slow. + * + * @param expression + * The expression to be evaluated + * @param expectedType + * The expected resulting type + * @param pageContext + * The page context + * @param functionMap + * Maps prefix and name to Method + * @return The result of the evaluation + */ + public static Object proprietaryEvaluate(final String expression, + final Class expectedType, final PageContext pageContext, + final ProtectedFunctionMapper functionMap, final boolean escape) + throws ELException { + Object retValue; + final ExpressionFactory exprFactory = jspf.getJspApplicationContext(pageContext.getServletContext()).getExpressionFactory(); + if (SecurityUtil.isPackageProtectionEnabled()) { + try { + retValue = AccessController + .doPrivileged(new PrivilegedExceptionAction() { + + public Object run() throws Exception { + ELContextImpl ctx = (ELContextImpl) pageContext.getELContext(); + ctx.setFunctionMapper(new FunctionMapperImpl(functionMap)); + ValueExpression ve = exprFactory.createValueExpression(ctx, expression, expectedType); + return ve.getValue(ctx); + } + }); + } catch (PrivilegedActionException ex) { + Exception realEx = ex.getException(); + if (realEx instanceof ELException) { + throw (ELException) realEx; + } else { + throw new ELException(realEx); + } + } + } else { + ELContextImpl ctx = (ELContextImpl) pageContext.getELContext(); + ctx.setFunctionMapper(new FunctionMapperImpl(functionMap)); + ValueExpression ve = exprFactory.createValueExpression(ctx, expression, expectedType); + retValue = ve.getValue(ctx); + } + if (escape && retValue != null) { + retValue = XmlEscape(retValue.toString()); + } + + return retValue; + } + + public ELContext getELContext() { + if (this.elContext == null) { + this.elContext = this.applicationContext.createELContext(this); + } + return this.elContext; + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/PerThreadTagHandlerPool.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/PerThreadTagHandlerPool.java new file mode 100644 index 000000000..58640efcf --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/PerThreadTagHandlerPool.java @@ -0,0 +1,134 @@ +/* + * 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.runtime; + +import java.util.Enumeration; +import java.util.Vector; + +import javax.servlet.ServletConfig; +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.tagext.Tag; + +import org.apache.jasper.Constants; + +/** + * Thread-local based pool of tag handlers that can be reused. + * + * @author Jan Luehe + * @author Costin Manolache + */ +public class PerThreadTagHandlerPool extends TagHandlerPool { + + private int maxSize; + + // For cleanup + private Vector perThreadDataVector; + + private ThreadLocal perThread; + + private static class PerThreadData { + Tag handlers[]; + int current; + } + + /** + * Constructs a tag handler pool with the default capacity. + */ + public PerThreadTagHandlerPool() { + super(); + perThreadDataVector = new Vector(); + } + + protected void init(ServletConfig config) { + maxSize = Constants.MAX_POOL_SIZE; + String maxSizeS = getOption(config, OPTION_MAXSIZE, null); + if (maxSizeS != null) { + maxSize = Integer.parseInt(maxSizeS); + if (maxSize < 0) { + maxSize = Constants.MAX_POOL_SIZE; + } + } + + perThread = new ThreadLocal() { + protected Object initialValue() { + PerThreadData ptd = new PerThreadData(); + ptd.handlers = new Tag[maxSize]; + ptd.current = -1; + perThreadDataVector.addElement(ptd); + return ptd; + } + }; + } + + /** + * Gets the next available tag handler from this tag handler pool, + * instantiating one if this tag handler pool is empty. + * + * @param handlerClass Tag handler class + * + * @return Reused or newly instantiated tag handler + * + * @throws JspException if a tag handler cannot be instantiated + */ + public Tag get(Class handlerClass) throws JspException { + PerThreadData ptd = (PerThreadData)perThread.get(); + if(ptd.current >=0 ) { + return ptd.handlers[ptd.current--]; + } else { + try { + return (Tag) handlerClass.newInstance(); + } catch (Exception e) { + throw new JspException(e.getMessage(), e); + } + } + } + + /** + * Adds the given tag handler to this tag handler pool, unless this tag + * handler pool has already reached its capacity, in which case the tag + * handler's release() method is called. + * + * @param handler Tag handler to add to this tag handler pool + */ + public void reuse(Tag handler) { + PerThreadData ptd=(PerThreadData)perThread.get(); + if (ptd.current < (ptd.handlers.length - 1)) { + ptd.handlers[++ptd.current] = handler; + } else { + handler.release(); + } + } + + /** + * Calls the release() method of all tag handlers in this tag handler pool. + */ + public void release() { + Enumeration enumeration = perThreadDataVector.elements(); + while (enumeration.hasMoreElements()) { + PerThreadData ptd = (PerThreadData)enumeration.nextElement(); + if (ptd.handlers != null) { + for (int i=ptd.current; i>=0; i--) { + if (ptd.handlers[i] != null) { + ptd.handlers[i].release(); + } + } + } + } + } +} + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/ProtectedFunctionMapper.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/ProtectedFunctionMapper.java new file mode 100644 index 000000000..309e21135 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/ProtectedFunctionMapper.java @@ -0,0 +1,196 @@ +/* + * 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.runtime; + +import java.util.HashMap; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.security.PrivilegedActionException; +import java.lang.reflect.Method; +import javax.servlet.jsp.el.FunctionMapper; + +import org.apache.jasper.security.SecurityUtil; + +/** + * Maps EL functions to their Java method counterparts. Keeps the actual Method + * objects protected so that JSP pages can't indirectly do reflection. + * + * @author Mark Roth + * @author Kin-man Chung + */ +public final class ProtectedFunctionMapper extends javax.el.FunctionMapper + implements FunctionMapper { + + /** + * Maps "prefix:name" to java.lang.Method objects. + */ + private HashMap fnmap = null; + + /** + * If there is only one function in the map, this is the Method for it. + */ + private Method theMethod = null; + + /** + * Constructor has protected access. + */ + private ProtectedFunctionMapper() { + } + + /** + * Generated Servlet and Tag Handler implementations call this method to + * retrieve an instance of the ProtectedFunctionMapper. This is necessary + * since generated code does not have access to create instances of classes + * in this package. + * + * @return A new protected function mapper. + */ + public static ProtectedFunctionMapper getInstance() { + ProtectedFunctionMapper funcMapper; + if (SecurityUtil.isPackageProtectionEnabled()) { + funcMapper = (ProtectedFunctionMapper) AccessController + .doPrivileged(new PrivilegedAction() { + public Object run() { + return new ProtectedFunctionMapper(); + } + }); + } else { + funcMapper = new ProtectedFunctionMapper(); + } + funcMapper.fnmap = new java.util.HashMap(); + return funcMapper; + } + + /** + * Stores a mapping from the given EL function prefix and name to the given + * Java method. + * + * @param fnQName + * The EL function qualified name (including prefix) + * @param c + * The class containing the Java method + * @param methodName + * The name of the Java method + * @param args + * The arguments of the Java method + * @throws RuntimeException + * if no method with the given signature could be found. + */ + public void mapFunction(String fnQName, final Class c, + final String methodName, final Class[] args) { + java.lang.reflect.Method method; + if (SecurityUtil.isPackageProtectionEnabled()) { + try { + method = (java.lang.reflect.Method) AccessController + .doPrivileged(new PrivilegedExceptionAction() { + + public Object run() throws Exception { + return c.getDeclaredMethod(methodName, args); + } + }); + } catch (PrivilegedActionException ex) { + throw new RuntimeException( + "Invalid function mapping - no such method: " + + ex.getException().getMessage()); + } + } else { + try { + method = c.getDeclaredMethod(methodName, args); + } catch (NoSuchMethodException e) { + throw new RuntimeException( + "Invalid function mapping - no such method: " + + e.getMessage()); + } + } + + this.fnmap.put(fnQName, method); + } + + /** + * Creates an instance for this class, and stores the Method for the given + * EL function prefix and name. This method is used for the case when there + * is only one function in the EL expression. + * + * @param fnQName + * The EL function qualified name (including prefix) + * @param c + * The class containing the Java method + * @param methodName + * The name of the Java method + * @param args + * The arguments of the Java method + * @throws RuntimeException + * if no method with the given signature could be found. + */ + public static ProtectedFunctionMapper getMapForFunction(String fnQName, + final Class c, final String methodName, final Class[] args) { + java.lang.reflect.Method method; + ProtectedFunctionMapper funcMapper; + if (SecurityUtil.isPackageProtectionEnabled()) { + funcMapper = (ProtectedFunctionMapper) AccessController + .doPrivileged(new PrivilegedAction() { + public Object run() { + return new ProtectedFunctionMapper(); + } + }); + + try { + method = (java.lang.reflect.Method) AccessController + .doPrivileged(new PrivilegedExceptionAction() { + + public Object run() throws Exception { + return c.getDeclaredMethod(methodName, args); + } + }); + } catch (PrivilegedActionException ex) { + throw new RuntimeException( + "Invalid function mapping - no such method: " + + ex.getException().getMessage()); + } + } else { + funcMapper = new ProtectedFunctionMapper(); + try { + method = c.getDeclaredMethod(methodName, args); + } catch (NoSuchMethodException e) { + throw new RuntimeException( + "Invalid function mapping - no such method: " + + e.getMessage()); + } + } + funcMapper.theMethod = method; + return funcMapper; + } + + /** + * Resolves the specified local name and prefix into a Java.lang.Method. + * Returns null if the prefix and local name are not found. + * + * @param prefix + * the prefix of the function + * @param localName + * the short name of the function + * @return the result of the method mapping. Null means no entry found. + */ + public Method resolveFunction(String prefix, String localName) { + if (this.fnmap != null) { + return (Method) this.fnmap.get(prefix + ":" + localName); + } + return theMethod; + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/ServletResponseWrapperInclude.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/ServletResponseWrapperInclude.java new file mode 100644 index 000000000..098e7038d --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/ServletResponseWrapperInclude.java @@ -0,0 +1,76 @@ +/* + * 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.runtime; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import javax.servlet.jsp.JspWriter; + +/** + * ServletResponseWrapper used by the JSP 'include' action. + * + * This wrapper response object is passed to RequestDispatcher.include(), so + * that the output of the included resource is appended to that of the + * including page. + * + * @author Pierre Delisle + */ + +public class ServletResponseWrapperInclude extends HttpServletResponseWrapper { + + /** + * PrintWriter which appends to the JspWriter of the including page. + */ + private PrintWriter printWriter; + + private JspWriter jspWriter; + + public ServletResponseWrapperInclude(ServletResponse response, + JspWriter jspWriter) { + super((HttpServletResponse)response); + this.printWriter = new PrintWriter(jspWriter); + this.jspWriter = jspWriter; + } + + /** + * Returns a wrapper around the JspWriter of the including page. + */ + public PrintWriter getWriter() throws IOException { + return printWriter; + } + + public ServletOutputStream getOutputStream() throws IOException { + throw new IllegalStateException(); + } + + /** + * Clears the output buffer of the JspWriter associated with the including + * page. + */ + public void resetBuffer() { + try { + jspWriter.clearBuffer(); + } catch (IOException ioe) { + } + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/TagHandlerPool.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/TagHandlerPool.java new file mode 100644 index 000000000..74f2b64bc --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/TagHandlerPool.java @@ -0,0 +1,191 @@ +/* + * 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.runtime; + +import javax.servlet.ServletConfig; +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.tagext.Tag; + +import org.apache.AnnotationProcessor; +import org.apache.jasper.Constants; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +/** + * Pool of tag handlers that can be reused. + * + * @author Jan Luehe + */ +public class TagHandlerPool { + + private Tag[] handlers; + + public static String OPTION_TAGPOOL="tagpoolClassName"; + public static String OPTION_MAXSIZE="tagpoolMaxSize"; + + private Log log = LogFactory.getLog(TagHandlerPool.class); + + // index of next available tag handler + private int current; + protected AnnotationProcessor annotationProcessor = null; + + public static TagHandlerPool getTagHandlerPool( ServletConfig config) { + TagHandlerPool result=null; + + String tpClassName=getOption( config, OPTION_TAGPOOL, null); + if( tpClassName != null ) { + try { + Class c=Class.forName( tpClassName ); + result=(TagHandlerPool)c.newInstance(); + } catch (Exception e) { + e.printStackTrace(); + result=null; + } + } + if( result==null ) result=new TagHandlerPool(); + result.init(config); + + return result; + } + + protected void init( ServletConfig config ) { + int maxSize=-1; + String maxSizeS=getOption(config, OPTION_MAXSIZE, null); + if( maxSizeS != null ) { + try { + maxSize=Integer.parseInt(maxSizeS); + } catch( Exception ex) { + maxSize=-1; + } + } + if( maxSize <0 ) { + maxSize=Constants.MAX_POOL_SIZE; + } + this.handlers = new Tag[maxSize]; + this.current = -1; + this.annotationProcessor = + (AnnotationProcessor) config.getServletContext().getAttribute(AnnotationProcessor.class.getName()); + } + + /** + * Constructs a tag handler pool with the default capacity. + */ + public TagHandlerPool() { + // Nothing - jasper generated servlets call the other constructor, + // this should be used in future + init . + } + + /** + * Constructs a tag handler pool with the given capacity. + * + * @param capacity Tag handler pool capacity + * @deprecated Use static getTagHandlerPool + */ + public TagHandlerPool(int capacity) { + this.handlers = new Tag[capacity]; + this.current = -1; + } + + /** + * Gets the next available tag handler from this tag handler pool, + * instantiating one if this tag handler pool is empty. + * + * @param handlerClass Tag handler class + * + * @return Reused or newly instantiated tag handler + * + * @throws JspException if a tag handler cannot be instantiated + */ + public Tag get(Class handlerClass) throws JspException { + Tag handler = null; + synchronized( this ) { + if (current >= 0) { + handler = handlers[current--]; + return handler; + } + } + + // Out of sync block - there is no need for other threads to + // wait for us to construct a tag for this thread. + try { + Tag instance = (Tag) handlerClass.newInstance(); + AnnotationHelper.postConstruct(annotationProcessor, instance); + return instance; + } catch (Exception e) { + throw new JspException(e.getMessage(), e); + } + } + + /** + * Adds the given tag handler to this tag handler pool, unless this tag + * handler pool has already reached its capacity, in which case the tag + * handler's release() method is called. + * + * @param handler Tag handler to add to this tag handler pool + */ + public void reuse(Tag handler) { + synchronized( this ) { + if (current < (handlers.length - 1)) { + handlers[++current] = handler; + return; + } + } + // There is no need for other threads to wait for us to release + handler.release(); + if (annotationProcessor != null) { + try { + AnnotationHelper.preDestroy(annotationProcessor, handler); + } catch (Exception e) { + log.warn("Error processing preDestroy on tag instance of " + + handler.getClass().getName(), e); + } + } + } + + /** + * Calls the release() method of all available tag handlers in this tag + * handler pool. + */ + public synchronized void release() { + for (int i = current; i >= 0; i--) { + handlers[i].release(); + if (annotationProcessor != null) { + try { + AnnotationHelper.preDestroy(annotationProcessor, handlers[i]); + } catch (Exception e) { + log.warn("Error processing preDestroy on tag instance of " + + handlers[i].getClass().getName(), e); + } + } + } + } + + protected static String getOption( ServletConfig config, String name, String defaultV) { + if( config == null ) return defaultV; + + String value=config.getInitParameter(name); + if( value != null ) return value; + if( config.getServletContext() ==null ) + return defaultV; + value=config.getServletContext().getInitParameter(name); + if( value!=null ) return value; + return defaultV; + } + +} + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/security/SecurityClassLoad.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/security/SecurityClassLoad.java new file mode 100644 index 000000000..6c7cfc347 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/security/SecurityClassLoad.java @@ -0,0 +1,111 @@ +/* + * 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.security; + +/** + * Static class used to preload java classes when using the + * Java SecurityManager so that the defineClassInPackage + * RuntimePermission does not trigger an AccessControlException. + * + * @author Jean-Francois Arcand + */ + +public final class SecurityClassLoad { + + private static org.apache.juli.logging.Log log= + org.apache.juli.logging.LogFactory.getLog( SecurityClassLoad.class ); + + public static void securityClassLoad(ClassLoader loader){ + + if( System.getSecurityManager() == null ){ + return; + } + + String basePackage = "org.apache.jasper."; + try { + loader.loadClass( basePackage + + "runtime.JspFactoryImpl$PrivilegedGetPageContext"); + loader.loadClass( basePackage + + "runtime.JspFactoryImpl$PrivilegedReleasePageContext"); + + loader.loadClass( basePackage + + "runtime.JspRuntimeLibrary"); + loader.loadClass( basePackage + + "runtime.JspRuntimeLibrary$PrivilegedIntrospectHelper"); + + loader.loadClass( basePackage + + "runtime.ServletResponseWrapperInclude"); + loader.loadClass( basePackage + + "runtime.TagHandlerPool"); + loader.loadClass( basePackage + + "runtime.JspFragmentHelper"); + + loader.loadClass( basePackage + + "runtime.ProtectedFunctionMapper"); + loader.loadClass( basePackage + + "runtime.ProtectedFunctionMapper$1"); + loader.loadClass( basePackage + + "runtime.ProtectedFunctionMapper$2"); + loader.loadClass( basePackage + + "runtime.ProtectedFunctionMapper$3"); + loader.loadClass( basePackage + + "runtime.ProtectedFunctionMapper$4"); + + loader.loadClass( basePackage + + "runtime.PageContextImpl"); + loader.loadClass( basePackage + + "runtime.PageContextImpl$1"); + loader.loadClass( basePackage + + "runtime.PageContextImpl$2"); + loader.loadClass( basePackage + + "runtime.PageContextImpl$3"); + loader.loadClass( basePackage + + "runtime.PageContextImpl$4"); + loader.loadClass( basePackage + + "runtime.PageContextImpl$5"); + loader.loadClass( basePackage + + "runtime.PageContextImpl$6"); + loader.loadClass( basePackage + + "runtime.PageContextImpl$7"); + loader.loadClass( basePackage + + "runtime.PageContextImpl$8"); + loader.loadClass( basePackage + + "runtime.PageContextImpl$9"); + loader.loadClass( basePackage + + "runtime.PageContextImpl$10"); + loader.loadClass( basePackage + + "runtime.PageContextImpl$11"); + loader.loadClass( basePackage + + "runtime.PageContextImpl$12"); + loader.loadClass( basePackage + + "runtime.PageContextImpl$13"); + + loader.loadClass( basePackage + + "runtime.JspContextWrapper"); + + loader.loadClass( basePackage + + "servlet.JspServletWrapper"); + + loader.loadClass( basePackage + + "runtime.JspWriterImpl$1"); + } catch (ClassNotFoundException ex) { + log.error("SecurityClassLoad", ex); + } + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/security/SecurityUtil.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/security/SecurityUtil.java new file mode 100644 index 000000000..22d0668e4 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/security/SecurityUtil.java @@ -0,0 +1,81 @@ +/* + * 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.security; + +import org.apache.jasper.Constants; + +/** + * Util class for Security related operations. + * + * @author Jean-Francois Arcand + */ + +public final class SecurityUtil{ + + private static boolean packageDefinitionEnabled = + System.getProperty("package.definition") == null ? false : true; + + /** + * Return the SecurityManager only if Security is enabled AND + * package protection mechanism is enabled. + */ + public static boolean isPackageProtectionEnabled(){ + if (packageDefinitionEnabled && Constants.IS_SECURITY_ENABLED){ + return true; + } + return false; + } + + + /** + * Filter the specified message string for characters that are sensitive + * in HTML. This avoids potential attacks caused by including JavaScript + * codes in the request URL that is often reported in error messages. + * + * @param message The message string to be filtered + */ + public static String filter(String message) { + + if (message == null) + return (null); + + char content[] = new char[message.length()]; + message.getChars(0, message.length(), content, 0); + StringBuffer result = new StringBuffer(content.length + 50); + for (int i = 0; i < content.length; i++) { + switch (content[i]) { + case '<': + result.append("<"); + break; + case '>': + result.append(">"); + break; + case '&': + result.append("&"); + break; + case '"': + result.append("""); + break; + default: + result.append(content[i]); + } + } + return (result.toString()); + + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JasperLoader.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JasperLoader.java new file mode 100644 index 000000000..7a3b0f72b --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JasperLoader.java @@ -0,0 +1,172 @@ +/* + * 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.servlet; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.CodeSource; +import java.security.PermissionCollection; + +import org.apache.jasper.Constants; + +/** + * Class loader for loading servlet class files (corresponding to JSP files) + * and tag handler class files (corresponding to tag files). + * + * @author Anil K. Vijendran + * @author Harish Prabandham + * @author Jean-Francois Arcand + */ +public class JasperLoader extends URLClassLoader { + + private PermissionCollection permissionCollection; + private CodeSource codeSource; + private String className; + private ClassLoader parent; + private SecurityManager securityManager; + + public JasperLoader(URL[] urls, ClassLoader parent, + PermissionCollection permissionCollection, + CodeSource codeSource) { + super(urls, parent); + this.permissionCollection = permissionCollection; + this.codeSource = codeSource; + this.parent = parent; + this.securityManager = System.getSecurityManager(); + } + + /** + * Load the class with the specified name. This method searches for + * classes in the same manner as loadClass(String, boolean) + * with false as the second argument. + * + * @param name Name of the class to be loaded + * + * @exception ClassNotFoundException if the class was not found + */ + public Class loadClass(String name) throws ClassNotFoundException { + + return (loadClass(name, false)); + } + + /** + * Load the class with the specified name, searching using the following + * algorithm until it finds and returns the class. If the class cannot + * be found, returns ClassNotFoundException. + *

    + *
  • Call findLoadedClass(String) to check if the + * class has already been loaded. If it has, the same + * Class object is returned.
  • + *
  • If the delegate property is set to true, + * call the loadClass() method of the parent class + * loader, if any.
  • + *
  • Call findClass() to find this class in our locally + * defined repositories.
  • + *
  • Call the loadClass() method of our parent + * class loader, if any.
  • + *
+ * If the class was found using the above steps, and the + * resolve flag is true, this method will then + * call resolveClass(Class) on the resulting Class object. + * + * @param name Name of the class to be loaded + * @param resolve If true then resolve the class + * + * @exception ClassNotFoundException if the class was not found + */ + public Class loadClass(final String name, boolean resolve) + throws ClassNotFoundException { + + Class clazz = null; + + // (0) Check our previously loaded class cache + clazz = findLoadedClass(name); + if (clazz != null) { + if (resolve) + resolveClass(clazz); + return (clazz); + } + + // (.5) Permission to access this class when using a SecurityManager + if (securityManager != null) { + int dot = name.lastIndexOf('.'); + if (dot >= 0) { + try { + // Do not call the security manager since by default, we grant that package. + if (!"org.apache.jasper.runtime".equalsIgnoreCase(name.substring(0,dot))){ + securityManager.checkPackageAccess(name.substring(0,dot)); + } + } catch (SecurityException se) { + String error = "Security Violation, attempt to use " + + "Restricted Class: " + name; + se.printStackTrace(); + throw new ClassNotFoundException(error); + } + } + } + + if( !name.startsWith(Constants.JSP_PACKAGE_NAME + '.') ) { + // Class is not in org.apache.jsp, therefore, have our + // parent load it + clazz = parent.loadClass(name); + if( resolve ) + resolveClass(clazz); + return clazz; + } + + return findClass(name); + } + + + /** + * Delegate to parent + * + * @see java.lang.ClassLoader#getResourceAsStream(java.lang.String) + */ + public InputStream getResourceAsStream(String name) { + InputStream is = parent.getResourceAsStream(name); + if (is == null) { + URL url = findResource(name); + if (url != null) { + try { + is = url.openStream(); + } catch (IOException e) { + is = null; + } + } + } + return is; + } + + + /** + * Get the Permissions for a CodeSource. + * + * Since this ClassLoader is only used for a JSP page in + * a web application context, we just return our preset + * PermissionCollection for the web app context. + * + * @param codeSource Code source where the code was loaded from + * @return PermissionCollection for CodeSource + */ + public final PermissionCollection getPermissions(CodeSource codeSource) { + return permissionCollection; + } +} \ No newline at end of file diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspCServletContext.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspCServletContext.java new file mode 100644 index 000000000..d0f324911 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspCServletContext.java @@ -0,0 +1,441 @@ +/* + * 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.servlet; + + +import java.io.File; +import java.io.InputStream; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Set; +import java.util.Vector; + +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + + +/** + * Simple ServletContext implementation without + * HTTP-specific methods. + * + * @author Peter Rossbach (pr@webapp.de) + */ + +public class JspCServletContext implements ServletContext { + + + // ----------------------------------------------------- Instance Variables + + + /** + * Servlet context attributes. + */ + protected Hashtable myAttributes; + + + /** + * The log writer we will write log messages to. + */ + protected PrintWriter myLogWriter; + + + /** + * The base URL (document root) for this context. + */ + protected URL myResourceBaseURL; + + + // ----------------------------------------------------------- Constructors + + + /** + * Create a new instance of this ServletContext implementation. + * + * @param aLogWriter PrintWriter which is used for log() calls + * @param aResourceBaseURL Resource base URL + */ + public JspCServletContext(PrintWriter aLogWriter, URL aResourceBaseURL) { + + myAttributes = new Hashtable(); + myLogWriter = aLogWriter; + myResourceBaseURL = aResourceBaseURL; + + } + + + // --------------------------------------------------------- Public Methods + + + /** + * Return the specified context attribute, if any. + * + * @param name Name of the requested attribute + */ + public Object getAttribute(String name) { + + return (myAttributes.get(name)); + + } + + + /** + * Return an enumeration of context attribute names. + */ + public Enumeration getAttributeNames() { + + return (myAttributes.keys()); + + } + + + /** + * Return the servlet context for the specified path. + * + * @param uripath Server-relative path starting with '/' + */ + public ServletContext getContext(String uripath) { + + return (null); + + } + + + /** + * Return the context path. + */ + public String getContextPath() { + + return (null); + + } + + + /** + * Return the specified context initialization parameter. + * + * @param name Name of the requested parameter + */ + public String getInitParameter(String name) { + + return (null); + + } + + + /** + * Return an enumeration of the names of context initialization + * parameters. + */ + public Enumeration getInitParameterNames() { + + return (new Vector().elements()); + + } + + + /** + * Return the Servlet API major version number. + */ + public int getMajorVersion() { + + return (2); + + } + + + /** + * Return the MIME type for the specified filename. + * + * @param file Filename whose MIME type is requested + */ + public String getMimeType(String file) { + + return (null); + + } + + + /** + * Return the Servlet API minor version number. + */ + public int getMinorVersion() { + + return (3); + + } + + + /** + * Return a request dispatcher for the specified servlet name. + * + * @param name Name of the requested servlet + */ + public RequestDispatcher getNamedDispatcher(String name) { + + return (null); + + } + + + /** + * Return the real path for the specified context-relative + * virtual path. + * + * @param path The context-relative virtual path to resolve + */ + public String getRealPath(String path) { + + if (!myResourceBaseURL.getProtocol().equals("file")) + return (null); + if (!path.startsWith("/")) + return (null); + try { + return + (getResource(path).getFile().replace('/', File.separatorChar)); + } catch (Throwable t) { + return (null); + } + + } + + + /** + * Return a request dispatcher for the specified context-relative path. + * + * @param path Context-relative path for which to acquire a dispatcher + */ + public RequestDispatcher getRequestDispatcher(String path) { + + return (null); + + } + + + /** + * Return a URL object of a resource that is mapped to the + * specified context-relative path. + * + * @param path Context-relative path of the desired resource + * + * @exception MalformedURLException if the resource path is + * not properly formed + */ + public URL getResource(String path) throws MalformedURLException { + + if (!path.startsWith("/")) + throw new MalformedURLException("Path '" + path + + "' does not start with '/'"); + URL url = new URL(myResourceBaseURL, path.substring(1)); + InputStream is = null; + try { + is = url.openStream(); + } catch (Throwable t) { + url = null; + } finally { + if (is != null) { + try { + is.close(); + } catch (Throwable t2) { + // Ignore + } + } + } + return url; + + } + + + /** + * Return an InputStream allowing access to the resource at the + * specified context-relative path. + * + * @param path Context-relative path of the desired resource + */ + public InputStream getResourceAsStream(String path) { + + try { + return (getResource(path).openStream()); + } catch (Throwable t) { + return (null); + } + + } + + + /** + * Return the set of resource paths for the "directory" at the + * specified context path. + * + * @param path Context-relative base path + */ + public Set getResourcePaths(String path) { + + Set thePaths = new HashSet(); + if (!path.endsWith("/")) + path += "/"; + String basePath = getRealPath(path); + if (basePath == null) + return (thePaths); + File theBaseDir = new File(basePath); + if (!theBaseDir.exists() || !theBaseDir.isDirectory()) + return (thePaths); + String theFiles[] = theBaseDir.list(); + for (int i = 0; i < theFiles.length; i++) { + File testFile = new File(basePath + File.separator + theFiles[i]); + if (testFile.isFile()) + thePaths.add(path + theFiles[i]); + else if (testFile.isDirectory()) + thePaths.add(path + theFiles[i] + "/"); + } + return (thePaths); + + } + + + /** + * Return descriptive information about this server. + */ + public String getServerInfo() { + + return ("JspCServletContext/1.0"); + + } + + + /** + * Return a null reference for the specified servlet name. + * + * @param name Name of the requested servlet + * + * @deprecated This method has been deprecated with no replacement + */ + public Servlet getServlet(String name) throws ServletException { + + return (null); + + } + + + /** + * Return the name of this servlet context. + */ + public String getServletContextName() { + + return (getServerInfo()); + + } + + + /** + * Return an empty enumeration of servlet names. + * + * @deprecated This method has been deprecated with no replacement + */ + public Enumeration getServletNames() { + + return (new Vector().elements()); + + } + + + /** + * Return an empty enumeration of servlets. + * + * @deprecated This method has been deprecated with no replacement + */ + public Enumeration getServlets() { + + return (new Vector().elements()); + + } + + + /** + * Log the specified message. + * + * @param message The message to be logged + */ + public void log(String message) { + + myLogWriter.println(message); + + } + + + /** + * Log the specified message and exception. + * + * @param exception The exception to be logged + * @param message The message to be logged + * + * @deprecated Use log(String,Throwable) instead + */ + public void log(Exception exception, String message) { + + log(message, exception); + + } + + + /** + * Log the specified message and exception. + * + * @param message The message to be logged + * @param exception The exception to be logged + */ + public void log(String message, Throwable exception) { + + myLogWriter.println(message); + exception.printStackTrace(myLogWriter); + + } + + + /** + * Remove the specified context attribute. + * + * @param name Name of the attribute to remove + */ + public void removeAttribute(String name) { + + myAttributes.remove(name); + + } + + + /** + * Set or replace the specified context attribute. + * + * @param name Name of the context attribute to set + * @param value Corresponding attribute value + */ + public void setAttribute(String name, Object value) { + + myAttributes.put(name, value); + + } + + + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspServlet.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspServlet.java new file mode 100644 index 000000000..1c1200358 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspServlet.java @@ -0,0 +1,347 @@ +/* + * 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.servlet; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.Enumeration; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.PeriodicEventListener; + +import org.apache.jasper.Constants; +import org.apache.jasper.EmbeddedServletOptions; +import org.apache.jasper.Options; +import org.apache.jasper.compiler.JspRuntimeContext; +import org.apache.jasper.compiler.Localizer; +import org.apache.jasper.security.SecurityUtil; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +/** + * The JSP engine (a.k.a Jasper). + * + * The servlet container is responsible for providing a + * URLClassLoader for the web application context Jasper + * is being used in. Jasper will try get the Tomcat + * ServletContext attribute for its ServletContext class + * loader, if that fails, it uses the parent class loader. + * In either case, it must be a URLClassLoader. + * + * @author Anil K. Vijendran + * @author Harish Prabandham + * @author Remy Maucherat + * @author Kin-man Chung + * @author Glenn Nielsen + */ +public class JspServlet extends HttpServlet implements PeriodicEventListener { + + // Logger + private Log log = LogFactory.getLog(JspServlet.class); + + private ServletContext context; + private ServletConfig config; + private Options options; + private JspRuntimeContext rctxt; + + + /* + * Initializes this JspServlet. + */ + public void init(ServletConfig config) throws ServletException { + + super.init(config); + this.config = config; + this.context = config.getServletContext(); + + // Initialize the JSP Runtime Context + // Check for a custom Options implementation + String engineOptionsName = + config.getInitParameter("engineOptionsClass"); + if (engineOptionsName != null) { + // Instantiate the indicated Options implementation + try { + ClassLoader loader = Thread.currentThread() + .getContextClassLoader(); + Class engineOptionsClass = loader.loadClass(engineOptionsName); + Class[] ctorSig = { ServletConfig.class, ServletContext.class }; + Constructor ctor = engineOptionsClass.getConstructor(ctorSig); + Object[] args = { config, context }; + options = (Options) ctor.newInstance(args); + } catch (Throwable e) { + // Need to localize this. + log.warn("Failed to load engineOptionsClass", e); + // Use the default Options implementation + options = new EmbeddedServletOptions(config, context); + } + } else { + // Use the default Options implementation + options = new EmbeddedServletOptions(config, context); + } + rctxt = new JspRuntimeContext(context, options); + + if (log.isDebugEnabled()) { + log.debug(Localizer.getMessage("jsp.message.scratch.dir.is", + options.getScratchDir().toString())); + log.debug(Localizer.getMessage("jsp.message.dont.modify.servlets")); + } + } + + + /** + * Returns the number of JSPs for which JspServletWrappers exist, i.e., + * the number of JSPs that have been loaded into the webapp with which + * this JspServlet is associated. + * + *

This info may be used for monitoring purposes. + * + * @return The number of JSPs that have been loaded into the webapp with + * which this JspServlet is associated + */ + public int getJspCount() { + return this.rctxt.getJspCount(); + } + + + /** + * Resets the JSP reload counter. + * + * @param count Value to which to reset the JSP reload counter + */ + public void setJspReloadCount(int count) { + this.rctxt.setJspReloadCount(count); + } + + + /** + * Gets the number of JSPs that have been reloaded. + * + *

This info may be used for monitoring purposes. + * + * @return The number of JSPs (in the webapp with which this JspServlet is + * associated) that have been reloaded + */ + public int getJspReloadCount() { + return this.rctxt.getJspReloadCount(); + } + + + /** + *

Look for a precompilation request as described in + * Section 8.4.2 of the JSP 1.2 Specification. WARNING - + * we cannot use request.getParameter() for this, because + * that will trigger parsing all of the request parameters, and not give + * a servlet the opportunity to call + * request.setCharacterEncoding() first.

+ * + * @param request The servlet requset we are processing + * + * @exception ServletException if an invalid parameter value for the + * jsp_precompile parameter name is specified + */ + boolean preCompile(HttpServletRequest request) throws ServletException { + + String queryString = request.getQueryString(); + if (queryString == null) { + return (false); + } + int start = queryString.indexOf(Constants.PRECOMPILE); + if (start < 0) { + return (false); + } + queryString = + queryString.substring(start + Constants.PRECOMPILE.length()); + if (queryString.length() == 0) { + return (true); // ?jsp_precompile + } + if (queryString.startsWith("&")) { + return (true); // ?jsp_precompile&foo=bar... + } + if (!queryString.startsWith("=")) { + return (false); // part of some other name or value + } + int limit = queryString.length(); + int ampersand = queryString.indexOf("&"); + if (ampersand > 0) { + limit = ampersand; + } + String value = queryString.substring(1, limit); + if (value.equals("true")) { + return (true); // ?jsp_precompile=true + } else if (value.equals("false")) { + // Spec says if jsp_precompile=false, the request should not + // be delivered to the JSP page; the easiest way to implement + // this is to set the flag to true, and precompile the page anyway. + // This still conforms to the spec, since it says the + // precompilation request can be ignored. + return (true); // ?jsp_precompile=false + } else { + throw new ServletException("Cannot have request parameter " + + Constants.PRECOMPILE + " set to " + + value); + } + + } + + + public void service (HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + + String jspUri = null; + + String jspFile = (String) request.getAttribute(Constants.JSP_FILE); + if (jspFile != null) { + // JSP is specified via in declaration + jspUri = jspFile; + } else { + /* + * Check to see if the requested JSP has been the target of a + * RequestDispatcher.include() + */ + jspUri = (String) request.getAttribute(Constants.INC_SERVLET_PATH); + if (jspUri != null) { + /* + * Requested JSP has been target of + * RequestDispatcher.include(). Its path is assembled from the + * relevant javax.servlet.include.* request attributes + */ + String pathInfo = (String) request.getAttribute( + "javax.servlet.include.path_info"); + if (pathInfo != null) { + jspUri += pathInfo; + } + } else { + /* + * Requested JSP has not been the target of a + * RequestDispatcher.include(). Reconstruct its path from the + * request's getServletPath() and getPathInfo() + */ + jspUri = request.getServletPath(); + String pathInfo = request.getPathInfo(); + if (pathInfo != null) { + jspUri += pathInfo; + } + } + } + + if (log.isDebugEnabled()) { + log.debug("JspEngine --> " + jspUri); + log.debug("\t ServletPath: " + request.getServletPath()); + log.debug("\t PathInfo: " + request.getPathInfo()); + log.debug("\t RealPath: " + context.getRealPath(jspUri)); + log.debug("\t RequestURI: " + request.getRequestURI()); + log.debug("\t QueryString: " + request.getQueryString()); + log.debug("\t Request Params: "); + Enumeration e = request.getParameterNames(); + while (e.hasMoreElements()) { + String name = (String) e.nextElement(); + log.debug("\t\t " + name + " = " + + request.getParameter(name)); + } + } + + try { + boolean precompile = preCompile(request); + serviceJspFile(request, response, jspUri, null, precompile); + } catch (RuntimeException e) { + throw e; + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Throwable e) { + throw new ServletException(e); + } + + } + + public void destroy() { + if (log.isDebugEnabled()) { + log.debug("JspServlet.destroy()"); + } + + rctxt.destroy(); + } + + + public void periodicEvent() { + rctxt.checkCompile(); + } + + // -------------------------------------------------------- Private Methods + + private void serviceJspFile(HttpServletRequest request, + HttpServletResponse response, String jspUri, + Throwable exception, boolean precompile) + throws ServletException, IOException { + + JspServletWrapper wrapper = + (JspServletWrapper) rctxt.getWrapper(jspUri); + if (wrapper == null) { + synchronized(this) { + wrapper = (JspServletWrapper) rctxt.getWrapper(jspUri); + if (wrapper == null) { + // Check if the requested JSP page exists, to avoid + // creating unnecessary directories and files. + if (null == context.getResource(jspUri)) { + String includeRequestUri = (String) + request.getAttribute( + "javax.servlet.include.request_uri"); + if (includeRequestUri != null) { + // This file was included. Throw an exception as + // a response.sendError() will be ignored + String msg = Localizer.getMessage( + "jsp.error.file.not.found",jspUri); + // Strictly, filtering this is an application + // responsibility but just in case... + throw new ServletException( + SecurityUtil.filter(msg)); + } else { + try { + response.sendError( + HttpServletResponse.SC_NOT_FOUND, + request.getRequestURI()); + } catch (IllegalStateException ise) { + log.error(Localizer.getMessage( + "jsp.error.file.not.found", + jspUri)); + } + } + return; + } + boolean isErrorPage = exception != null; + wrapper = new JspServletWrapper(config, options, jspUri, + isErrorPage, rctxt); + rctxt.addWrapper(jspUri,wrapper); + } + } + } + + wrapper.service(request, response, precompile); + + } + + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspServletWrapper.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspServletWrapper.java new file mode 100644 index 000000000..48db4230e --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspServletWrapper.java @@ -0,0 +1,527 @@ +/* + * 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.servlet; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URL; + +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.SingleThreadModel; +import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.jsp.tagext.TagInfo; + +import org.apache.AnnotationProcessor; +import org.apache.jasper.JasperException; +import org.apache.jasper.JspCompilationContext; +import org.apache.jasper.Options; +import org.apache.jasper.compiler.ErrorDispatcher; +import org.apache.jasper.compiler.JavacErrorDetail; +import org.apache.jasper.compiler.JspRuntimeContext; +import org.apache.jasper.compiler.Localizer; +import org.apache.jasper.runtime.JspSourceDependent; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +/** + * The JSP engine (a.k.a Jasper). + * + * The servlet container is responsible for providing a + * URLClassLoader for the web application context Jasper + * is being used in. Jasper will try get the Tomcat + * ServletContext attribute for its ServletContext class + * loader, if that fails, it uses the parent class loader. + * In either case, it must be a URLClassLoader. + * + * @author Anil K. Vijendran + * @author Harish Prabandham + * @author Remy Maucherat + * @author Kin-man Chung + * @author Glenn Nielsen + * @author Tim Fennell + */ + +public class JspServletWrapper { + + // Logger + private Log log = LogFactory.getLog(JspServletWrapper.class); + + private Servlet theServlet; + private String jspUri; + private Class servletClass; + private Class tagHandlerClass; + private JspCompilationContext ctxt; + private long available = 0L; + private ServletConfig config; + private Options options; + private boolean firstTime = true; + private boolean reload = true; + private boolean isTagFile; + private int tripCount; + private JasperException compileException; + private long servletClassLastModifiedTime; + private long lastModificationTest = 0L; + + /* + * JspServletWrapper for JSP pages. + */ + public JspServletWrapper(ServletConfig config, Options options, String jspUri, + boolean isErrorPage, JspRuntimeContext rctxt) + throws JasperException { + + this.isTagFile = false; + this.config = config; + this.options = options; + this.jspUri = jspUri; + ctxt = new JspCompilationContext(jspUri, isErrorPage, options, + config.getServletContext(), + this, rctxt); + } + + /* + * JspServletWrapper for tag files. + */ + public JspServletWrapper(ServletContext servletContext, + Options options, + String tagFilePath, + TagInfo tagInfo, + JspRuntimeContext rctxt, + URL tagFileJarUrl) + throws JasperException { + + this.isTagFile = true; + this.config = null; // not used + this.options = options; + this.jspUri = tagFilePath; + this.tripCount = 0; + ctxt = new JspCompilationContext(jspUri, tagInfo, options, + servletContext, this, rctxt, + tagFileJarUrl); + } + + public JspCompilationContext getJspEngineContext() { + return ctxt; + } + + public void setReload(boolean reload) { + this.reload = reload; + } + + public Servlet getServlet() + throws ServletException, IOException, FileNotFoundException + { + if (reload) { + synchronized (this) { + // Synchronizing on jsw enables simultaneous loading + // of different pages, but not the same page. + if (reload) { + // This is to maintain the original protocol. + destroy(); + + Servlet servlet = null; + + try { + servletClass = ctxt.load(); + servlet = (Servlet) servletClass.newInstance(); + AnnotationProcessor annotationProcessor = (AnnotationProcessor) config.getServletContext().getAttribute(AnnotationProcessor.class.getName()); + if (annotationProcessor != null) { + annotationProcessor.processAnnotations(servlet); + annotationProcessor.postConstruct(servlet); + } + } catch (IllegalAccessException e) { + throw new JasperException(e); + } catch (InstantiationException e) { + throw new JasperException(e); + } catch (Exception e) { + throw new JasperException(e); + } + + servlet.init(config); + + if (!firstTime) { + ctxt.getRuntimeContext().incrementJspReloadCount(); + } + + theServlet = servlet; + reload = false; + } + } + } + return theServlet; + } + + public ServletContext getServletContext() { + return config.getServletContext(); + } + + /** + * Sets the compilation exception for this JspServletWrapper. + * + * @param je The compilation exception + */ + public void setCompilationException(JasperException je) { + this.compileException = je; + } + + /** + * Sets the last-modified time of the servlet class file associated with + * this JspServletWrapper. + * + * @param lastModified Last-modified time of servlet class + */ + public void setServletClassLastModifiedTime(long lastModified) { + if (this.servletClassLastModifiedTime < lastModified) { + synchronized (this) { + if (this.servletClassLastModifiedTime < lastModified) { + this.servletClassLastModifiedTime = lastModified; + reload = true; + } + } + } + } + + /** + * Compile (if needed) and load a tag file + */ + public Class loadTagFile() throws JasperException { + + try { + if (ctxt.isRemoved()) { + throw new FileNotFoundException(jspUri); + } + if (options.getDevelopment() || firstTime ) { + synchronized (this) { + firstTime = false; + ctxt.compile(); + } + } else { + if (compileException != null) { + throw compileException; + } + } + + if (reload) { + tagHandlerClass = ctxt.load(); + reload = false; + } + } catch (FileNotFoundException ex) { + throw new JasperException(ex); + } + + return tagHandlerClass; + } + + /** + * Compile and load a prototype for the Tag file. This is needed + * when compiling tag files with circular dependencies. A prototpe + * (skeleton) with no dependencies on other other tag files is + * generated and compiled. + */ + public Class loadTagFilePrototype() throws JasperException { + + ctxt.setPrototypeMode(true); + try { + return loadTagFile(); + } finally { + ctxt.setPrototypeMode(false); + } + } + + /** + * Get a list of files that the current page has source dependency on. + */ + public java.util.List getDependants() { + try { + Object target; + if (isTagFile) { + if (reload) { + tagHandlerClass = ctxt.load(); + reload = false; + } + target = tagHandlerClass.newInstance(); + } else { + target = getServlet(); + } + if (target != null && target instanceof JspSourceDependent) { + return ((java.util.List) ((JspSourceDependent) target).getDependants()); + } + } catch (Throwable ex) { + } + return null; + } + + public boolean isTagFile() { + return this.isTagFile; + } + + public int incTripCount() { + return tripCount++; + } + + public int decTripCount() { + return tripCount--; + } + + public void service(HttpServletRequest request, + HttpServletResponse response, + boolean precompile) + throws ServletException, IOException, FileNotFoundException { + + try { + + if (ctxt.isRemoved()) { + throw new FileNotFoundException(jspUri); + } + + if ((available > 0L) && (available < Long.MAX_VALUE)) { + if (available > System.currentTimeMillis()) { + response.setDateHeader("Retry-After", available); + response.sendError + (HttpServletResponse.SC_SERVICE_UNAVAILABLE, + Localizer.getMessage("jsp.error.unavailable")); + return; + } else { + // Wait period has expired. Reset. + available = 0; + } + } + + /* + * (1) Compile + */ + if (options.getDevelopment() || firstTime ) { + synchronized (this) { + firstTime = false; + + // The following sets reload to true, if necessary + ctxt.compile(); + } + } else { + if (compileException != null) { + // Throw cached compilation exception + throw compileException; + } + } + + /* + * (2) (Re)load servlet class file + */ + getServlet(); + + // If a page is to be precompiled only, return. + if (precompile) { + return; + } + + } catch (ServletException ex) { + if (options.getDevelopment()) { + throw handleJspException(ex); + } else { + throw ex; + } + } catch (IOException ex) { + if (options.getDevelopment()) { + throw handleJspException(ex); + } else { + throw ex; + } + } catch (IllegalStateException ex) { + if (options.getDevelopment()) { + throw handleJspException(ex); + } else { + throw ex; + } + } catch (Exception ex) { + if (options.getDevelopment()) { + throw handleJspException(ex); + } else { + throw new JasperException(ex); + } + } + + try { + + /* + * (3) Service request + */ + if (theServlet instanceof SingleThreadModel) { + // sync on the wrapper so that the freshness + // of the page is determined right before servicing + synchronized (this) { + theServlet.service(request, response); + } + } else { + theServlet.service(request, response); + } + + } catch (UnavailableException ex) { + String includeRequestUri = (String) + request.getAttribute("javax.servlet.include.request_uri"); + if (includeRequestUri != null) { + // This file was included. Throw an exception as + // a response.sendError() will be ignored by the + // servlet engine. + throw ex; + } else { + int unavailableSeconds = ex.getUnavailableSeconds(); + if (unavailableSeconds <= 0) { + unavailableSeconds = 60; // Arbitrary default + } + available = System.currentTimeMillis() + + (unavailableSeconds * 1000L); + response.sendError + (HttpServletResponse.SC_SERVICE_UNAVAILABLE, + ex.getMessage()); + } + } catch (ServletException ex) { + if(options.getDevelopment()) { + throw handleJspException(ex); + } else { + throw ex; + } + } catch (IOException ex) { + if(options.getDevelopment()) { + throw handleJspException(ex); + } else { + throw ex; + } + } catch (IllegalStateException ex) { + if(options.getDevelopment()) { + throw handleJspException(ex); + } else { + throw ex; + } + } catch (Exception ex) { + if(options.getDevelopment()) { + throw handleJspException(ex); + } else { + throw new JasperException(ex); + } + } + } + + public void destroy() { + if (theServlet != null) { + theServlet.destroy(); + AnnotationProcessor annotationProcessor = (AnnotationProcessor) config.getServletContext().getAttribute(AnnotationProcessor.class.getName()); + if (annotationProcessor != null) { + try { + annotationProcessor.preDestroy(theServlet); + } catch (Exception e) { + // Log any exception, since it can't be passed along + log.error(Localizer.getMessage("jsp.error.file.not.found", + e.getMessage()), e); + } + } + } + } + + /** + * @return Returns the lastModificationTest. + */ + public long getLastModificationTest() { + return lastModificationTest; + } + /** + * @param lastModificationTest The lastModificationTest to set. + */ + public void setLastModificationTest(long lastModificationTest) { + this.lastModificationTest = lastModificationTest; + } + + /** + *

Attempts to construct a JasperException that contains helpful information + * about what went wrong. Uses the JSP compiler system to translate the line + * number in the generated servlet that originated the exception to a line + * number in the JSP. Then constructs an exception containing that + * information, and a snippet of the JSP to help debugging. + * Please see http://issues.apache.org/bugzilla/show_bug.cgi?id=37062 and + * http://www.tfenne.com/jasper/ for more details. + *

+ * + * @param ex the exception that was the cause of the problem. + * @return a JasperException with more detailed information + */ + protected JasperException handleJspException(Exception ex) { + try { + Throwable realException = ex; + if (ex instanceof ServletException) { + realException = ((ServletException) ex).getRootCause(); + } + + // First identify the stack frame in the trace that represents the JSP + StackTraceElement[] frames = realException.getStackTrace(); + StackTraceElement jspFrame = null; + + for (int i=0; i + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/Util.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/Util.java new file mode 100644 index 000000000..dfe0f2f5a --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/Util.java @@ -0,0 +1,328 @@ +/* + * 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.tagplugins.jstl; + +import org.apache.jasper.Constants; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.util.Locale; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.JspTagException; +import javax.servlet.jsp.PageContext; + +/** + * Util contains some often used consts, static methods and embedded class + * to support the JSTL tag plugin. + */ + +public class Util { + + public static final String VALID_SCHEME_CHAR = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+.-"; + + public static final String DEFAULT_ENCODING = + "ISO-8859-1"; + + public static final int HIGHEST_SPECIAL = '>'; + + public static char[][] specialCharactersRepresentation = new char[HIGHEST_SPECIAL + 1][]; + + static { + specialCharactersRepresentation['&'] = "&".toCharArray(); + specialCharactersRepresentation['<'] = "<".toCharArray(); + specialCharactersRepresentation['>'] = ">".toCharArray(); + specialCharactersRepresentation['"'] = """.toCharArray(); + specialCharactersRepresentation['\''] = "'".toCharArray(); + } + + /** + * Converts the given string description of a scope to the corresponding + * PageContext constant. + * + * The validity of the given scope has already been checked by the + * appropriate TLV. + * + * @param scope String description of scope + * + * @return PageContext constant corresponding to given scope description + * + * taken from org.apache.taglibs.standard.tag.common.core.Util + */ + public static int getScope(String scope){ + int ret = PageContext.PAGE_SCOPE; + + if("request".equalsIgnoreCase(scope)){ + ret = PageContext.REQUEST_SCOPE; + }else if("session".equalsIgnoreCase(scope)){ + ret = PageContext.SESSION_SCOPE; + }else if("application".equalsIgnoreCase(scope)){ + ret = PageContext.APPLICATION_SCOPE; + } + + return ret; + } + + /** + * Returns true if our current URL is absolute, + * false otherwise. + * taken from org.apache.taglibs.standard.tag.common.core.ImportSupport + */ + public static boolean isAbsoluteUrl(String url){ + if(url == null){ + return false; + } + + int colonPos = url.indexOf(":"); + if(colonPos == -1){ + return false; + } + + for(int i=0;iurl. The session ID + * is encoded as a URL "path parameter" beginning with "jsessionid=". + * We thus remove anything we find between ";jsessionid=" (inclusive) + * and either EOS or a subsequent ';' (exclusive). + * + * taken from org.apache.taglibs.standard.tag.common.core.ImportSupport + */ + public static String stripSession(String url) { + StringBuffer u = new StringBuffer(url); + int sessionStart; + while ((sessionStart = u.toString().indexOf(";" + Constants.SESSION_PARAMETER_NAME + "=")) != -1) { + int sessionEnd = u.toString().indexOf(";", sessionStart + 1); + if (sessionEnd == -1) + sessionEnd = u.toString().indexOf("?", sessionStart + 1); + if (sessionEnd == -1) // still + sessionEnd = u.length(); + u.delete(sessionStart, sessionEnd); + } + return u.toString(); + } + + + /** + * Performs the following substring replacements + * (to facilitate output to XML/HTML pages): + * + * & -> & + * < -> < + * > -> > + * " -> " + * ' -> ' + * + * See also OutSupport.writeEscapedXml(). + * + * taken from org.apache.taglibs.standard.tag.common.core.Util + */ + public static String escapeXml(String buffer) { + int start = 0; + int length = buffer.length(); + char[] arrayBuffer = buffer.toCharArray(); + StringBuffer escapedBuffer = null; + + for (int i = 0; i < length; i++) { + char c = arrayBuffer[i]; + if (c <= HIGHEST_SPECIAL) { + char[] escaped = specialCharactersRepresentation[c]; + if (escaped != null) { + // create StringBuffer to hold escaped xml string + if (start == 0) { + escapedBuffer = new StringBuffer(length + 5); + } + // add unescaped portion + if (start < i) { + escapedBuffer.append(arrayBuffer,start,i-start); + } + start = i + 1; + // add escaped xml + escapedBuffer.append(escaped); + } + } + } + // no xml escaping was necessary + if (start == 0) { + return buffer; + } + // add rest of unescaped portion + if (start < length) { + escapedBuffer.append(arrayBuffer,start,length-start); + } + return escapedBuffer.toString(); + } + + /** Utility methods + * taken from org.apache.taglibs.standard.tag.common.core.UrlSupport + */ + public static String resolveUrl( + String url, String context, PageContext pageContext) + throws JspException { + // don't touch absolute URLs + if (isAbsoluteUrl(url)) + return url; + + // normalize relative URLs against a context root + HttpServletRequest request = + (HttpServletRequest) pageContext.getRequest(); + if (context == null) { + if (url.startsWith("/")) + return (request.getContextPath() + url); + else + return url; + } else { + if (!context.startsWith("/") || !url.startsWith("/")) { + throw new JspTagException( + "In URL tags, when the \"context\" attribute is specified, values of both \"context\" and \"url\" must start with \"/\"."); + } + if (context.equals("/")) { + // Don't produce string starting with '//', many + // browsers interpret this as host name, not as + // path on same host. + return url; + } else { + return (context + url); + } + } + } + + /** Wraps responses to allow us to retrieve results as Strings. + * mainly taken from org.apache.taglibs.standard.tag.common.core.importSupport + */ + public static class ImportResponseWrapper extends HttpServletResponseWrapper{ + + private StringWriter sw = new StringWriter(); + private ByteArrayOutputStream bos = new ByteArrayOutputStream(); + private ServletOutputStream sos = new ServletOutputStream() { + public void write(int b) throws IOException { + bos.write(b); + } + }; + private boolean isWriterUsed; + private boolean isStreamUsed; + private int status = 200; + private String charEncoding; + + public ImportResponseWrapper(HttpServletResponse arg0) { + super(arg0); + // TODO Auto-generated constructor stub + } + + public PrintWriter getWriter() { + if (isStreamUsed) + throw new IllegalStateException("Unexpected internal error during <import>: " + + "Target servlet called getWriter(), then getOutputStream()"); + isWriterUsed = true; + return new PrintWriter(sw); + } + + public ServletOutputStream getOutputStream() { + if (isWriterUsed) + throw new IllegalStateException("Unexpected internal error during <import>: " + + "Target servlet called getOutputStream(), then getWriter()"); + isStreamUsed = true; + return sos; + } + + /** Has no effect. */ + public void setContentType(String x) { + // ignore + } + + /** Has no effect. */ + public void setLocale(Locale x) { + // ignore + } + + public void setStatus(int status) { + this.status = status; + } + + public int getStatus() { + return status; + } + + public String getCharEncoding(){ + return this.charEncoding; + } + + public void setCharEncoding(String ce){ + this.charEncoding = ce; + } + + public String getString() throws UnsupportedEncodingException { + if (isWriterUsed) + return sw.toString(); + else if (isStreamUsed) { + if (this.charEncoding != null && !this.charEncoding.equals("")) + return bos.toString(charEncoding); + else + return bos.toString("ISO-8859-1"); + } else + return ""; // target didn't write anything + } + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Catch.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Catch.java new file mode 100644 index 000000000..c7d154aa4 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Catch.java @@ -0,0 +1,72 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.TagPlugin; +import org.apache.jasper.compiler.tagplugin.TagPluginContext; + +public class Catch implements TagPlugin { + + public void doTag(TagPluginContext ctxt) { + + //flag for the existence of the var attribute + boolean hasVar = ctxt.isAttributeSpecified("var"); + + //temp name for exception and caught + String exceptionName = ctxt.getTemporaryVariableName(); + String caughtName = ctxt.getTemporaryVariableName(); + + //main part to generate code + ctxt.generateJavaSource("boolean " + caughtName + " = false;"); + ctxt.generateJavaSource("try{"); + ctxt.generateBody(); + ctxt.generateJavaSource("}"); + + //do catch + ctxt.generateJavaSource("catch(Throwable " + exceptionName + "){"); + + //if the var specified, the exception object should + //be set to the attribute "var" defines in page scope + if(hasVar){ + String strVar = ctxt.getConstantAttribute("var"); + ctxt.generateJavaSource(" pageContext.setAttribute(\"" + strVar + "\", " + + exceptionName + ", PageContext.PAGE_SCOPE);"); + } + + //whenever there's exception caught, + //the flag caught should be set true; + ctxt.generateJavaSource(" " + caughtName + " = true;"); + ctxt.generateJavaSource("}"); + + //do finally + ctxt.generateJavaSource("finally{"); + + //if var specified, the attribute it defines + //in page scope should be removed + if(hasVar){ + String strVar = ctxt.getConstantAttribute("var"); + ctxt.generateJavaSource(" if(!" + caughtName + "){"); + ctxt.generateJavaSource(" pageContext.removeAttribute(\"" + strVar + "\", PageContext.PAGE_SCOPE);"); + ctxt.generateJavaSource(" }"); + } + + ctxt.generateJavaSource("}"); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Choose.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Choose.java new file mode 100644 index 000000000..7622ceea2 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Choose.java @@ -0,0 +1,34 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.*; + +public final class Choose implements TagPlugin { + + public void doTag(TagPluginContext ctxt) { + + // Not much to do here, much of the work will be done in the + // containing tags, and . + + ctxt.generateBody(); + // See comments in When.java for the reason "}" is generated here. + ctxt.generateJavaSource("}"); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForEach.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForEach.java new file mode 100644 index 000000000..49c9a3fcd --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForEach.java @@ -0,0 +1,344 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.*; + +public final class ForEach implements TagPlugin { + + private boolean hasVar, hasBegin, hasEnd, hasStep; + + public void doTag(TagPluginContext ctxt) { + + String index = null; + + boolean hasVarStatus = ctxt.isAttributeSpecified("varStatus"); + if (hasVarStatus) { + ctxt.dontUseTagPlugin(); + return; + } + + hasVar = ctxt.isAttributeSpecified("var"); + hasBegin = ctxt.isAttributeSpecified("begin"); + hasEnd = ctxt.isAttributeSpecified("end"); + hasStep = ctxt.isAttributeSpecified("step"); + + boolean hasItems = ctxt.isAttributeSpecified("items"); + if (hasItems) { + doCollection(ctxt); + return; + } + + // We must have a begin and end attributes + index = ctxt.getTemporaryVariableName(); + ctxt.generateJavaSource("for (int " + index + " = "); + ctxt.generateAttribute("begin"); + ctxt.generateJavaSource("; " + index + " <= "); + ctxt.generateAttribute("end"); + if (hasStep) { + ctxt.generateJavaSource("; " + index + "+="); + ctxt.generateAttribute("step"); + ctxt.generateJavaSource(") {"); + } + else { + ctxt.generateJavaSource("; " + index + "++) {"); + } + + // If var is specified and the body contains an EL, then sycn + // the var attribute + if (hasVar /* && ctxt.hasEL() */) { + ctxt.generateJavaSource("_jspx_page_context.setAttribute("); + ctxt.generateAttribute("var"); + ctxt.generateJavaSource(", String.valueOf(" + index + "));"); + } + ctxt.generateBody(); + ctxt.generateJavaSource("}"); + } + + /** + * Generate codes for Collections + * The pseudo code is: + */ + private void doCollection(TagPluginContext ctxt) { + + ctxt.generateImport("java.util.*"); + generateIterators(ctxt); + + String itemsV = ctxt.getTemporaryVariableName(); + ctxt.generateJavaSource("Object " + itemsV + "= "); + ctxt.generateAttribute("items"); + ctxt.generateJavaSource(";"); + + String indexV=null, beginV=null, endV=null, stepV=null; + if (hasBegin) { + beginV = ctxt.getTemporaryVariableName(); + ctxt.generateJavaSource("int " + beginV + " = "); + ctxt.generateAttribute("begin"); + ctxt.generateJavaSource(";"); + } + if (hasEnd) { + indexV = ctxt.getTemporaryVariableName(); + ctxt.generateJavaSource("int " + indexV + " = 0;"); + endV = ctxt.getTemporaryVariableName(); + ctxt.generateJavaSource("int " + endV + " = "); + ctxt.generateAttribute("end"); + ctxt.generateJavaSource(";"); + } + if (hasStep) { + stepV = ctxt.getTemporaryVariableName(); + ctxt.generateJavaSource("int " + stepV + " = "); + ctxt.generateAttribute("step"); + ctxt.generateJavaSource(";"); + } + + String iterV = ctxt.getTemporaryVariableName(); + ctxt.generateJavaSource("Iterator " + iterV + " = null;"); + // Object[] + ctxt.generateJavaSource("if (" + itemsV + " instanceof Object[])"); + ctxt.generateJavaSource(iterV + "=toIterator((Object[])" + itemsV + ");"); + // boolean[] + ctxt.generateJavaSource("else if (" + itemsV + " instanceof boolean[])"); + ctxt.generateJavaSource(iterV + "=toIterator((boolean[])" + itemsV + ");"); + // byte[] + ctxt.generateJavaSource("else if (" + itemsV + " instanceof byte[])"); + ctxt.generateJavaSource(iterV + "=toIterator((byte[])" + itemsV + ");"); + // char[] + ctxt.generateJavaSource("else if (" + itemsV + " instanceof char[])"); + ctxt.generateJavaSource(iterV + "=toIterator((char[])" + itemsV + ");"); + // short[] + ctxt.generateJavaSource("else if (" + itemsV + " instanceof short[])"); + ctxt.generateJavaSource(iterV + "=toIterator((short[])" + itemsV + ");"); + // int[] + ctxt.generateJavaSource("else if (" + itemsV + " instanceof int[])"); + ctxt.generateJavaSource(iterV + "=toIterator((int[])" + itemsV + ");"); + // long[] + ctxt.generateJavaSource("else if (" + itemsV + " instanceof long[])"); + ctxt.generateJavaSource(iterV + "=toIterator((long[])" + itemsV + ");"); + // float[] + ctxt.generateJavaSource("else if (" + itemsV + " instanceof float[])"); + ctxt.generateJavaSource(iterV + "=toIterator((float[])" + itemsV + ");"); + // double[] + ctxt.generateJavaSource("else if (" + itemsV + " instanceof double[])"); + ctxt.generateJavaSource(iterV + "=toIterator((double[])" + itemsV + ");"); + + // Collection + ctxt.generateJavaSource("else if (" + itemsV + " instanceof Collection)"); + ctxt.generateJavaSource(iterV + "=((Collection)" + itemsV + ").iterator();"); + + // Iterator + ctxt.generateJavaSource("else if (" + itemsV + " instanceof Iterator)"); + ctxt.generateJavaSource(iterV + "=(Iterator)" + itemsV + ";"); + + // Enumeration + ctxt.generateJavaSource("else if (" + itemsV + " instanceof Enumeration)"); + ctxt.generateJavaSource(iterV + "=toIterator((Enumeration)" + itemsV + ");"); + + // Map + ctxt.generateJavaSource("else if (" + itemsV + " instanceof Map)"); + ctxt.generateJavaSource(iterV + "=((Map)" + itemsV + ").entrySet().iterator();"); + + if (hasBegin) { + String tV = ctxt.getTemporaryVariableName(); + ctxt.generateJavaSource("for (int " + tV + "=" + beginV + ";" + + tV + ">0 && " + iterV + ".hasNext(); " + + tV + "--)"); + ctxt.generateJavaSource(iterV + ".next();"); + } + + ctxt.generateJavaSource("while (" + iterV + ".hasNext()){"); + if (hasVar) { + ctxt.generateJavaSource("_jspx_page_context.setAttribute("); + ctxt.generateAttribute("var"); + ctxt.generateJavaSource(", " + iterV + ".next());"); + } + + ctxt.generateBody(); + + if (hasStep) { + String tV = ctxt.getTemporaryVariableName(); + ctxt.generateJavaSource("for (int " + tV + "=" + stepV + "-1;" + + tV + ">0 && " + iterV + ".hasNext(); " + + tV + "--)"); + ctxt.generateJavaSource(iterV + ".next();"); + } + if (hasEnd) { + if (hasStep) { + ctxt.generateJavaSource(indexV + "+=" + stepV + ";"); + } + else { + ctxt.generateJavaSource(indexV + "++;"); + } + if (hasBegin) { + ctxt.generateJavaSource("if(" + beginV + "+" + indexV + + ">"+ endV + ")"); + } + else { + ctxt.generateJavaSource("if(" + indexV + ">" + endV + ")"); + } + ctxt.generateJavaSource("break;"); + } + ctxt.generateJavaSource("}"); // while + } + + /** + * Generate iterators for data types supported in items + */ + private void generateIterators(TagPluginContext ctxt) { + + // Object[] + ctxt.generateDeclaration("ObjectArrayIterator", + "private Iterator toIterator(final Object[] a){\n" + + " return (new Iterator() {\n" + + " int index=0;\n" + + " public boolean hasNext() {\n" + + " return index < a.length;}\n" + + " public Object next() {\n" + + " return a[index++];}\n" + + " public void remove() {}\n" + + " });\n" + + "}" + ); + + // boolean[] + ctxt.generateDeclaration("booleanArrayIterator", + "private Iterator toIterator(final boolean[] a){\n" + + " return (new Iterator() {\n" + + " int index=0;\n" + + " public boolean hasNext() {\n" + + " return index < a.length;}\n" + + " public Object next() {\n" + + " return new Boolean(a[index++]);}\n" + + " public void remove() {}\n" + + " });\n" + + "}" + ); + + // byte[] + ctxt.generateDeclaration("byteArrayIterator", + "private Iterator toIterator(final byte[] a){\n" + + " return (new Iterator() {\n" + + " int index=0;\n" + + " public boolean hasNext() {\n" + + " return index < a.length;}\n" + + " public Object next() {\n" + + " return new Byte(a[index++]);}\n" + + " public void remove() {}\n" + + " });\n" + + "}" + ); + + // char[] + ctxt.generateDeclaration("charArrayIterator", + "private Iterator toIterator(final char[] a){\n" + + " return (new Iterator() {\n" + + " int index=0;\n" + + " public boolean hasNext() {\n" + + " return index < a.length;}\n" + + " public Object next() {\n" + + " return new Character(a[index++]);}\n" + + " public void remove() {}\n" + + " });\n" + + "}" + ); + + // short[] + ctxt.generateDeclaration("shortArrayIterator", + "private Iterator toIterator(final short[] a){\n" + + " return (new Iterator() {\n" + + " int index=0;\n" + + " public boolean hasNext() {\n" + + " return index < a.length;}\n" + + " public Object next() {\n" + + " return new Short(a[index++]);}\n" + + " public void remove() {}\n" + + " });\n" + + "}" + ); + + // int[] + ctxt.generateDeclaration("intArrayIterator", + "private Iterator toIterator(final int[] a){\n" + + " return (new Iterator() {\n" + + " int index=0;\n" + + " public boolean hasNext() {\n" + + " return index < a.length;}\n" + + " public Object next() {\n" + + " return new Integer(a[index++]);}\n" + + " public void remove() {}\n" + + " });\n" + + "}" + ); + + // long[] + ctxt.generateDeclaration("longArrayIterator", + "private Iterator toIterator(final long[] a){\n" + + " return (new Iterator() {\n" + + " int index=0;\n" + + " public boolean hasNext() {\n" + + " return index < a.length;}\n" + + " public Object next() {\n" + + " return new Long(a[index++]);}\n" + + " public void remove() {}\n" + + " });\n" + + "}" + ); + + // float[] + ctxt.generateDeclaration("floatArrayIterator", + "private Iterator toIterator(final float[] a){\n" + + " return (new Iterator() {\n" + + " int index=0;\n" + + " public boolean hasNext() {\n" + + " return index < a.length;}\n" + + " public Object next() {\n" + + " return new Float(a[index++]);}\n" + + " public void remove() {}\n" + + " });\n" + + "}" + ); + + // double[] + ctxt.generateDeclaration("doubleArrayIterator", + "private Iterator toIterator(final double[] a){\n" + + " return (new Iterator() {\n" + + " int index=0;\n" + + " public boolean hasNext() {\n" + + " return index < a.length;}\n" + + " public Object next() {\n" + + " return new Double(a[index++]);}\n" + + " public void remove() {}\n" + + " });\n" + + "}" + ); + + // Enumeration + ctxt.generateDeclaration("enumIterator", + "private Iterator toIterator(final Enumeration e){\n" + + " return (new Iterator() {\n" + + " public boolean hasNext() {\n" + + " return e.hasMoreElements();}\n" + + " public Object next() {\n" + + " return e.nextElement();}\n" + + " public void remove() {}\n" + + " });\n" + + "}" + ); + + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForTokens.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForTokens.java new file mode 100644 index 000000000..c72f0ad21 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForTokens.java @@ -0,0 +1,119 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.TagPlugin; +import org.apache.jasper.compiler.tagplugin.TagPluginContext; + +public class ForTokens implements TagPlugin { + + public void doTag(TagPluginContext ctxt) { + boolean hasVar, hasVarStatus, hasBegin, hasEnd, hasStep; + + //init the flags + hasVar = ctxt.isAttributeSpecified("var"); + hasVarStatus = ctxt.isAttributeSpecified("varStatus"); + hasBegin = ctxt.isAttributeSpecified("begin"); + hasEnd = ctxt.isAttributeSpecified("end"); + hasStep = ctxt.isAttributeSpecified("step"); + + if(hasVarStatus){ + ctxt.dontUseTagPlugin(); + return; + } + + //define all the temp variables' names + String itemsName = ctxt.getTemporaryVariableName(); + String delimsName = ctxt.getTemporaryVariableName(); + String stName = ctxt.getTemporaryVariableName(); + String beginName = ctxt.getTemporaryVariableName(); + String endName = ctxt.getTemporaryVariableName(); + String stepName = ctxt.getTemporaryVariableName(); + String index = ctxt.getTemporaryVariableName(); + String temp = ctxt.getTemporaryVariableName(); + String tokensCountName = ctxt.getTemporaryVariableName(); + + //get the value of the "items" attribute + ctxt.generateJavaSource("String " + itemsName + " = (String)"); + ctxt.generateAttribute("items"); + ctxt.generateJavaSource(";"); + + //get the value of the "delim" attribute + ctxt.generateJavaSource("String " + delimsName + " = (String)"); + ctxt.generateAttribute("delims"); + ctxt.generateJavaSource(";"); + + //new a StringTokenizer Object according to the "items" and the "delim" + ctxt.generateJavaSource("java.util.StringTokenizer " + stName + " = " + + "new java.util.StringTokenizer(" + itemsName + ", " + delimsName + ");"); + + //if "begin" specified, move the token to the "begin" place + //and record the begin index. default begin place is 0. + ctxt.generateJavaSource("int " + tokensCountName + " = " + stName + ".countTokens();"); + if(hasBegin){ + ctxt.generateJavaSource("int " + beginName + " = " ); + ctxt.generateAttribute("begin"); + ctxt.generateJavaSource(";"); + ctxt.generateJavaSource("for(int " + index + " = 0; " + index + " < " + beginName + " && " + stName + ".hasMoreTokens(); " + index + "++, " + stName + ".nextToken()){}"); + }else{ + ctxt.generateJavaSource("int " + beginName + " = 0;"); + } + + //when "end" is specified, if the "end" is more than the last index, + //record the end place as the last index, otherwise, record it as "end"; + //default end place is the last index + if(hasEnd){ + ctxt.generateJavaSource("int " + endName + " = 0;" ); + ctxt.generateJavaSource("if((" + tokensCountName + " - 1) < "); + ctxt.generateAttribute("end"); + ctxt.generateJavaSource("){"); + ctxt.generateJavaSource(" " + endName + " = " + tokensCountName + " - 1;"); + ctxt.generateJavaSource("}else{"); + ctxt.generateJavaSource(" " + endName + " = "); + ctxt.generateAttribute("end"); + ctxt.generateJavaSource(";}"); + }else{ + ctxt.generateJavaSource("int " + endName + " = " + tokensCountName + " - 1;"); + } + + //get the step value from "step" if specified. + //default step value is 1. + if(hasStep){ + ctxt.generateJavaSource("int " + stepName + " = " ); + ctxt.generateAttribute("step"); + ctxt.generateJavaSource(";"); + }else{ + ctxt.generateJavaSource("int " + stepName + " = 1;"); + } + + //the loop + ctxt.generateJavaSource("for(int " + index + " = " + beginName + "; " + index + " <= " + endName + "; " + index + "++){"); + ctxt.generateJavaSource(" String " + temp + " = " + stName + ".nextToken();"); + ctxt.generateJavaSource(" if(((" + index + " - " + beginName + ") % " + stepName + ") == 0){"); + //if var specified, put the current token into the attribute "var" defines. + if(hasVar){ + String strVar = ctxt.getConstantAttribute("var"); + ctxt.generateJavaSource(" pageContext.setAttribute(\"" + strVar + "\", " + temp + ");"); + } + ctxt.generateBody(); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource("}"); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/If.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/If.java new file mode 100644 index 000000000..5f5a6df44 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/If.java @@ -0,0 +1,50 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.*; + +public final class If implements TagPlugin { + + public void doTag(TagPluginContext ctxt) { + String condV = ctxt.getTemporaryVariableName(); + ctxt.generateJavaSource("boolean " + condV + "="); + ctxt.generateAttribute("test"); + ctxt.generateJavaSource(";"); + if (ctxt.isAttributeSpecified("var")) { + String scope = "PageContext.PAGE_SCOPE"; + if (ctxt.isAttributeSpecified("scope")) { + String scopeStr = ctxt.getConstantAttribute("scope"); + if ("request".equals(scopeStr)) { + scope = "PageContext.REQUEST_SCOPE"; + } else if ("session".equals(scopeStr)) { + scope = "PageContext.SESSION_SCOPE"; + } else if ("application".equals(scopeStr)) { + scope = "PageContext.APPLICATION_SCOPE"; + } + } + ctxt.generateJavaSource("_jspx_page_context.setAttribute("); + ctxt.generateAttribute("var"); + ctxt.generateJavaSource(", new Boolean(" + condV + ")," + scope + ");"); + } + ctxt.generateJavaSource("if (" + condV + "){"); + ctxt.generateBody(); + ctxt.generateJavaSource("}"); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Import.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Import.java new file mode 100644 index 000000000..080342932 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Import.java @@ -0,0 +1,382 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.TagPlugin; +import org.apache.jasper.compiler.tagplugin.TagPluginContext; +import org.apache.jasper.tagplugins.jstl.Util; + +public class Import implements TagPlugin { + + public void doTag(TagPluginContext ctxt) { + boolean hasContext, hasVar, hasScope, hasVarReader, hasCharEncoding; + + //flags + hasContext = ctxt.isAttributeSpecified("context"); + hasVar = ctxt.isAttributeSpecified("var"); + hasScope = ctxt.isAttributeSpecified("scope"); + hasVarReader = ctxt.isAttributeSpecified("varReader"); + hasCharEncoding = ctxt.isAttributeSpecified("charEncoding"); + + //variables' names + String urlName = ctxt.getTemporaryVariableName(); + String contextName = ctxt.getTemporaryVariableName(); + String iauName = ctxt.getTemporaryVariableName(); // is absolute url + String urlObjName = ctxt.getTemporaryVariableName(); //URL object + String ucName = ctxt.getTemporaryVariableName(); //URLConnection + String inputStreamName = ctxt.getTemporaryVariableName(); + String tempReaderName = ctxt.getTemporaryVariableName(); + String tempReaderName2 = ctxt.getTemporaryVariableName(); + String charSetName = ctxt.getTemporaryVariableName(); + String charEncodingName = ctxt.getTemporaryVariableName(); + String contentTypeName = ctxt.getTemporaryVariableName(); + String varReaderName = ctxt.getTemporaryVariableName(); + String servletContextName = ctxt.getTemporaryVariableName(); + String servletPathName = ctxt.getTemporaryVariableName(); + String requestDispatcherName = ctxt.getTemporaryVariableName(); + String irwName = ctxt.getTemporaryVariableName(); //ImportResponseWrapper name + String brName = ctxt.getTemporaryVariableName(); //BufferedReader name + String sbName = ctxt.getTemporaryVariableName(); //StringBuffer name + String tempStringName = ctxt.getTemporaryVariableName(); + + //is absolute url + ctxt.generateJavaSource("boolean " + iauName + ";"); + + //get the url value + ctxt.generateJavaSource("String " + urlName + " = "); + ctxt.generateAttribute("url"); + ctxt.generateJavaSource(";"); + + //validate the url + ctxt.generateJavaSource("if(" + urlName + " == null || " + urlName + ".equals(\"\")){"); + ctxt.generateJavaSource(" throw new JspTagException(\"The \\\"url\\\" attribute " + + "illegally evaluated to \\\"null\\\" or \\\"\\\" in <import>\");"); + ctxt.generateJavaSource("}"); + + //initialize the is_absolute_url + ctxt.generateJavaSource(iauName + " = " + + "org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl(" + urlName + ");"); + + //validate the context + if(hasContext){ + + ctxt.generateJavaSource("String " + contextName + " = "); + ctxt.generateAttribute("context"); + ctxt.generateJavaSource(";"); + + ctxt.generateJavaSource("if((!" + contextName + ".startsWith(\"/\")) " + + "|| (!" + urlName + ".startsWith(\"/\"))){"); + ctxt.generateJavaSource(" throw new JspTagException" + + "(\"In URL tags, when the \\\"context\\\" attribute is specified, " + + "values of both \\\"context\\\" and \\\"url\\\" must start with \\\"/\\\".\");"); + ctxt.generateJavaSource("}"); + + } + + //define charset + ctxt.generateJavaSource("String " + charSetName + " = null;"); + + //if the charEncoding attribute is specified + if(hasCharEncoding){ + + //initialize the charEncoding + ctxt.generateJavaSource("String " + charEncodingName + " = "); + ctxt.generateAttribute("charEncoding"); + ctxt.generateJavaSource(";"); + + //assign appropriate value tp the charset + ctxt.generateJavaSource("if(null != " + charEncodingName + " " + + "&& !" + charEncodingName + ".equals(\"\")){"); + ctxt.generateJavaSource(" " + charSetName + " = " + + charEncodingName + ";"); + ctxt.generateJavaSource("}"); + } + + //reshape the url string + ctxt.generateJavaSource("if(!"+iauName+"){"); + ctxt.generateJavaSource(" if(!" + urlName + ".startsWith(\"/\")){"); + ctxt.generateJavaSource(" String " + servletPathName + " = " + + "((HttpServletRequest)pageContext.getRequest()).getServletPath();"); + ctxt.generateJavaSource(" " + urlName + " = " + + servletPathName + ".substring(0," + servletPathName + ".lastIndexOf('/')) + '/' + " + urlName + ";"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource("}"); + + //if the varReader attribute specified + if(hasVarReader){ + + //get the String value of varReader + ctxt.generateJavaSource("String " + varReaderName + " = "); + ctxt.generateAttribute("varReader"); + ctxt.generateJavaSource(";"); + + //if the url is absolute url + ctxt.generateJavaSource("if(" + iauName + "){"); + + //get the content of the target + ctxt.generateJavaSource(" java.net.URL " + urlObjName + " = new java.net.URL(" + urlName + ");"); + ctxt.generateJavaSource(" java.net.URLConnection " + ucName + " = " + + urlObjName + ".openConnection();"); + ctxt.generateJavaSource(" java.io.InputStream " + inputStreamName + " = " + + ucName + ".getInputStream();"); + + ctxt.generateJavaSource(" if(" + charSetName + " == null){"); + ctxt.generateJavaSource(" String " + contentTypeName + " = " + + ucName + ".getContentType();"); + ctxt.generateJavaSource(" if(null != " + contentTypeName + "){"); + ctxt.generateJavaSource(" " + charSetName + " = " + + "org.apache.jasper.tagplugins.jstl.Util.getContentTypeAttribute(" + contentTypeName + ", \"charset\");"); + ctxt.generateJavaSource(" if(" + charSetName + " == null) " + + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;"); + ctxt.generateJavaSource(" }else{"); + ctxt.generateJavaSource(" " + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" }"); + + if(!hasCharEncoding){ + ctxt.generateJavaSource(" String " + contentTypeName + " = " + ucName + ".getContentType();"); + } + + //define the Reader + ctxt.generateJavaSource(" java.io.Reader " + tempReaderName + " = null;"); + + //initialize the Reader object + ctxt.generateJavaSource(" try{"); + ctxt.generateJavaSource(" " + tempReaderName + " = new java.io.InputStreamReader(" + inputStreamName + ", " + charSetName + ");"); + ctxt.generateJavaSource(" }catch(Exception ex){"); + ctxt.generateJavaSource(" " + tempReaderName + " = new java.io.InputStreamReader(" + inputStreamName + ", org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING);"); + ctxt.generateJavaSource(" }"); + + //validate the response + ctxt.generateJavaSource(" if(" + ucName + " instanceof java.net.HttpURLConnection){"); + ctxt.generateJavaSource(" int status = ((java.net.HttpURLConnection) " + ucName + ").getResponseCode();"); + ctxt.generateJavaSource(" if(status < 200 || status > 299){"); + ctxt.generateJavaSource(" throw new JspTagException(status + \" \" + " + urlName + ");"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" }"); + + //set attribute in the page context scope + ctxt.generateJavaSource(" pageContext.setAttribute(" + varReaderName + ", " + tempReaderName + ");"); + + //if the url is relative + ctxt.generateJavaSource("}else{"); + + //if the url is relative, http request is needed + ctxt.generateJavaSource(" if (!(pageContext.getRequest() instanceof HttpServletRequest " + + "&& pageContext.getResponse() instanceof HttpServletResponse)){"); + ctxt.generateJavaSource(" throw new JspTagException(\"Relative <import> from non-HTTP request not allowed\");"); + ctxt.generateJavaSource(" }"); + + //get the servlet context of the context defined in the context attribute + ctxt.generateJavaSource(" ServletContext " + servletContextName + " = null;"); + if(hasContext){ + ctxt.generateJavaSource(" if(null != " + contextName + "){"); + ctxt.generateJavaSource(" " + servletContextName + " = pageContext.getServletContext().getContext(" + contextName + ");" ); + ctxt.generateJavaSource(" }else{"); + ctxt.generateJavaSource(" " + servletContextName + " = pageContext.getServletContext();"); + ctxt.generateJavaSource(" }"); + }else{ + ctxt.generateJavaSource(" " + servletContextName + " = pageContext.getServletContext();"); + } + + // + ctxt.generateJavaSource(" if(" + servletContextName + " == null){"); + if(hasContext){ + ctxt.generateJavaSource(" throw new JspTagException(\"Unable to get RequestDispatcher for Context: \\\" \"+" + contextName + "+\" \\\" and URL: \\\" \" +" + urlName + "+ \" \\\". Verify values and/or enable cross context access.\");"); + }else{ + ctxt.generateJavaSource(" throw new JspTagException(\"Unable to get RequestDispatcher for URL: \\\" \" +" + urlName + "+ \" \\\". Verify values and/or enable cross context access.\");"); + } + ctxt.generateJavaSource(" }"); + + //get the request dispatcher + ctxt.generateJavaSource(" RequestDispatcher " + requestDispatcherName + " = " + servletContextName + ".getRequestDispatcher(org.apache.jasper.tagplugins.jstl.Util.stripSession("+urlName+"));"); + ctxt.generateJavaSource(" if(" + requestDispatcherName + " == null) throw new JspTagException(org.apache.jasper.tagplugins.jstl.Util.stripSession("+urlName+"));"); + + //initialize a ImportResponseWrapper to include the resource + ctxt.generateJavaSource(" org.apache.jasper.tagplugins.jstl.Util.ImportResponseWrapper " + irwName + " = new org.apache.jasper.tagplugins.jstl.Util.ImportResponseWrapper((HttpServletResponse) pageContext.getResponse());"); + ctxt.generateJavaSource(" if(" + charSetName + " == null){"); + ctxt.generateJavaSource(" " + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" " + irwName + ".setCharEncoding(" + charSetName + ");"); + ctxt.generateJavaSource(" try{"); + ctxt.generateJavaSource(" " + requestDispatcherName + ".include(pageContext.getRequest(), " + irwName + ");"); + ctxt.generateJavaSource(" }catch(java.io.IOException ex){"); + ctxt.generateJavaSource(" throw new JspException(ex);"); + ctxt.generateJavaSource(" }catch(RuntimeException ex){"); + ctxt.generateJavaSource(" throw new JspException(ex);"); + ctxt.generateJavaSource(" }catch(ServletException ex){"); + ctxt.generateJavaSource(" Throwable rc = ex.getRootCause();"); + ctxt.generateJavaSource(" if (rc == null)"); + ctxt.generateJavaSource(" throw new JspException(ex);"); + ctxt.generateJavaSource(" else"); + ctxt.generateJavaSource(" throw new JspException(rc);"); + ctxt.generateJavaSource(" }"); + + //validate the response status + ctxt.generateJavaSource(" if(" + irwName + ".getStatus() < 200 || " + irwName + ".getStatus() > 299){"); + ctxt.generateJavaSource(" throw new JspTagException(" + irwName + ".getStatus()+\" \" + org.apache.jasper.tagplugins.jstl.Util.stripSession(" + urlName + "));"); + ctxt.generateJavaSource(" }"); + + //push in the page context + ctxt.generateJavaSource(" java.io.Reader " + tempReaderName + " = new java.io.StringReader(" + irwName + ".getString());"); + ctxt.generateJavaSource(" pageContext.setAttribute(" + varReaderName + ", " + tempReaderName + ");"); + + ctxt.generateJavaSource("}"); + + //execute the body action + ctxt.generateBody(); + + //close the reader + ctxt.generateJavaSource("java.io.Reader " + tempReaderName2 + " = (java.io.Reader)pageContext.getAttribute(" + varReaderName + ");"); + ctxt.generateJavaSource("if(" + tempReaderName2 + " != null) " + tempReaderName2 + ".close();"); + ctxt.generateJavaSource("pageContext.removeAttribute(" + varReaderName + ",1);"); + } + + //if the varReader is not specified + else{ + + ctxt.generateJavaSource("pageContext.setAttribute(\"url_without_param\"," + urlName + ");"); + ctxt.generateBody(); + ctxt.generateJavaSource(urlName + " = (String)pageContext.getAttribute(\"url_without_param\");"); + ctxt.generateJavaSource("pageContext.removeAttribute(\"url_without_param\");"); + String strScope = "page"; + if(hasScope){ + strScope = ctxt.getConstantAttribute("scope"); + } + int iScope = Util.getScope(strScope); + + ctxt.generateJavaSource("String " + tempStringName + " = null;"); + + ctxt.generateJavaSource("if(" + iauName + "){"); + + //get the content of the target + ctxt.generateJavaSource(" java.net.URL " + urlObjName + " = new java.net.URL(" + urlName + ");"); + ctxt.generateJavaSource(" java.net.URLConnection " + ucName + " = " + urlObjName + ".openConnection();"); + ctxt.generateJavaSource(" java.io.InputStream " + inputStreamName + " = " + ucName + ".getInputStream();"); + ctxt.generateJavaSource(" java.io.Reader " + tempReaderName + " = null;"); + + ctxt.generateJavaSource(" if(" + charSetName + " == null){"); + ctxt.generateJavaSource(" String " + contentTypeName + " = " + + ucName + ".getContentType();"); + ctxt.generateJavaSource(" if(null != " + contentTypeName + "){"); + ctxt.generateJavaSource(" " + charSetName + " = " + + "org.apache.jasper.tagplugins.jstl.Util.getContentTypeAttribute(" + contentTypeName + ", \"charset\");"); + ctxt.generateJavaSource(" if(" + charSetName + " == null) " + + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;"); + ctxt.generateJavaSource(" }else{"); + ctxt.generateJavaSource(" " + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" }"); + + ctxt.generateJavaSource(" try{"); + ctxt.generateJavaSource(" " + tempReaderName + " = new java.io.InputStreamReader(" + inputStreamName + "," + charSetName + ");"); + ctxt.generateJavaSource(" }catch(Exception ex){"); + //ctxt.generateJavaSource(" throw new JspTagException(ex.toString());"); + ctxt.generateJavaSource(" " + tempReaderName + " = new java.io.InputStreamReader(" + inputStreamName + ",org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING);"); + ctxt.generateJavaSource(" }"); + + //validate the response + ctxt.generateJavaSource(" if(" + ucName + " instanceof java.net.HttpURLConnection){"); + ctxt.generateJavaSource(" int status = ((java.net.HttpURLConnection) " + ucName + ").getResponseCode();"); + ctxt.generateJavaSource(" if(status < 200 || status > 299){"); + ctxt.generateJavaSource(" throw new JspTagException(status + \" \" + " + urlName + ");"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" }"); + + ctxt.generateJavaSource(" java.io.BufferedReader " + brName + " = new java.io.BufferedReader(" + tempReaderName + ");"); + ctxt.generateJavaSource(" StringBuffer " + sbName + " = new StringBuffer();"); + String index = ctxt.getTemporaryVariableName(); + ctxt.generateJavaSource(" int " + index + ";"); + ctxt.generateJavaSource(" while(("+index+" = "+brName+".read()) != -1) "+sbName+".append((char)"+index+");"); + ctxt.generateJavaSource(" " + tempStringName + " = " +sbName + ".toString();"); + + ctxt.generateJavaSource("}else{"); + + //if the url is relative, http request is needed. + ctxt.generateJavaSource(" if (!(pageContext.getRequest() instanceof HttpServletRequest " + + "&& pageContext.getResponse() instanceof HttpServletResponse)){"); + ctxt.generateJavaSource(" throw new JspTagException(\"Relative <import> from non-HTTP request not allowed\");"); + ctxt.generateJavaSource(" }"); + + //get the servlet context of the context defined in the context attribute + ctxt.generateJavaSource(" ServletContext " + servletContextName + " = null;"); + if(hasContext){ + ctxt.generateJavaSource(" if(null != " + contextName + "){"); + ctxt.generateJavaSource(" " + servletContextName + " = pageContext.getServletContext().getContext(" + contextName + ");" ); + ctxt.generateJavaSource(" }else{"); + ctxt.generateJavaSource(" " + servletContextName + " = pageContext.getServletContext();"); + ctxt.generateJavaSource(" }"); + }else{ + ctxt.generateJavaSource(" " + servletContextName + " = pageContext.getServletContext();"); + } + + // + ctxt.generateJavaSource(" if(" + servletContextName + " == null){"); + if(hasContext){ + ctxt.generateJavaSource(" throw new JspTagException(\"Unable to get RequestDispatcher for Context: \\\" \" +" + contextName + "+ \" \\\" and URL: \\\" \" +" + urlName + "+ \" \\\". Verify values and/or enable cross context access.\");"); + }else{ + ctxt.generateJavaSource(" throw new JspTagException(\"Unable to get RequestDispatcher for URL: \\\" \" +" + urlName + "+ \" \\\". Verify values and/or enable cross context access.\");"); + } + ctxt.generateJavaSource(" }"); + + //get the request dispatcher + ctxt.generateJavaSource(" RequestDispatcher " + requestDispatcherName + " = " + servletContextName + ".getRequestDispatcher(org.apache.jasper.tagplugins.jstl.Util.stripSession("+urlName+"));"); + ctxt.generateJavaSource(" if(" + requestDispatcherName + " == null) throw new JspTagException(org.apache.jasper.tagplugins.jstl.Util.stripSession("+urlName+"));"); + + //initialize a ImportResponseWrapper to include the resource + ctxt.generateJavaSource(" org.apache.jasper.tagplugins.jstl.Util.ImportResponseWrapper " + irwName + " = new org.apache.jasper.tagplugins.jstl.Util.ImportResponseWrapper((HttpServletResponse) pageContext.getResponse());"); + ctxt.generateJavaSource(" if(" + charSetName + " == null){"); + ctxt.generateJavaSource(" " + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" " + irwName + ".setCharEncoding(" + charSetName + ");"); + ctxt.generateJavaSource(" try{"); + ctxt.generateJavaSource(" " + requestDispatcherName + ".include(pageContext.getRequest(), " + irwName + ");"); + ctxt.generateJavaSource(" }catch(java.io.IOException ex){"); + ctxt.generateJavaSource(" throw new JspException(ex);"); + ctxt.generateJavaSource(" }catch(RuntimeException ex){"); + ctxt.generateJavaSource(" throw new JspException(ex);"); + ctxt.generateJavaSource(" }catch(ServletException ex){"); + ctxt.generateJavaSource(" Throwable rc = ex.getRootCause();"); + ctxt.generateJavaSource(" if (rc == null)"); + ctxt.generateJavaSource(" throw new JspException(ex);"); + ctxt.generateJavaSource(" else"); + ctxt.generateJavaSource(" throw new JspException(rc);"); + ctxt.generateJavaSource(" }"); + + //validate the response status + ctxt.generateJavaSource(" if(" + irwName + ".getStatus() < 200 || " + irwName + ".getStatus() > 299){"); + ctxt.generateJavaSource(" throw new JspTagException(" + irwName + ".getStatus()+\" \" + org.apache.jasper.tagplugins.jstl.Util.stripSession(" + urlName + "));"); + ctxt.generateJavaSource(" }"); + + ctxt.generateJavaSource(" " + tempStringName + " = " + irwName + ".getString();"); + + ctxt.generateJavaSource("}"); + + if(hasVar){ + String strVar = ctxt.getConstantAttribute("var"); + ctxt.generateJavaSource("pageContext.setAttribute(\""+strVar+"\"," + tempStringName + "," + iScope + ");"); + }else{ + ctxt.generateJavaSource("pageContext.getOut().print(" + tempStringName + ");"); + } + } + } + + + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Otherwise.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Otherwise.java new file mode 100644 index 000000000..3119d3027 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Otherwise.java @@ -0,0 +1,32 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.*; + +public final class Otherwise implements TagPlugin { + + public void doTag(TagPluginContext ctxt) { + + // See When.java for the reason whey "}" is need at the beginng and + // not at the end. + ctxt.generateJavaSource("} else {"); + ctxt.generateBody(); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Out.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Out.java new file mode 100644 index 000000000..92ac1fe03 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Out.java @@ -0,0 +1,90 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.TagPlugin; +import org.apache.jasper.compiler.tagplugin.TagPluginContext; + + +public final class Out implements TagPlugin { + + public void doTag(TagPluginContext ctxt) { + + //these two data member are to indicate + //whether the corresponding attribute is specified + boolean hasDefault=false, hasEscapeXml=false; + hasDefault = ctxt.isAttributeSpecified("default"); + hasEscapeXml = ctxt.isAttributeSpecified("escapeXml"); + + //strValName, strEscapeXmlName & strDefName are two variables' name + //standing for value, escapeXml and default attribute + String strValName = ctxt.getTemporaryVariableName(); + String strDefName = ctxt.getTemporaryVariableName(); + String strEscapeXmlName = ctxt.getTemporaryVariableName(); + + //according to the tag file, the value attribute is mandatory. + ctxt.generateJavaSource("String " + strValName + " = null;"); + ctxt.generateJavaSource("if("); + ctxt.generateAttribute("value"); + ctxt.generateJavaSource("!=null){"); + ctxt.generateJavaSource(" " + strValName + " = ("); + ctxt.generateAttribute("value"); + ctxt.generateJavaSource(").toString();"); + ctxt.generateJavaSource("}"); + + //initiate the strDefName with null. + //if the default has been specified, then assign the value to it; + ctxt.generateJavaSource("String " + strDefName + " = null;\n"); + if(hasDefault){ + ctxt.generateJavaSource("if("); + ctxt.generateAttribute("default"); + ctxt.generateJavaSource(" != null){"); + ctxt.generateJavaSource(strDefName + " = ("); + ctxt.generateAttribute("default"); + ctxt.generateJavaSource(").toString();"); + ctxt.generateJavaSource("}"); + } + + //initiate the strEscapeXmlName with true; + //if the escapeXml is specified, assign the value to it; + ctxt.generateJavaSource("boolean " + strEscapeXmlName + " = true;"); + if(hasEscapeXml){ + ctxt.generateJavaSource(strEscapeXmlName + " = Boolean.parseBoolean(("); + ctxt.generateAttribute("default"); + ctxt.generateJavaSource(").toString());"); + } + + //main part. + ctxt.generateJavaSource("if(null != " + strValName +"){"); + ctxt.generateJavaSource(" if(" + strEscapeXmlName + "){"); + ctxt.generateJavaSource(" " + strValName + " = org.apache.jasper.tagplugins.jstl.Util.escapeXml(" + strValName + ");"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" out.write(" + strValName + ");"); + ctxt.generateJavaSource("}else{"); + ctxt.generateJavaSource(" if(null != " + strDefName + "){"); + ctxt.generateJavaSource(" if(" + strEscapeXmlName + "){"); + ctxt.generateJavaSource(" " + strDefName + " = org.apache.jasper.tagplugins.jstl.Util.escapeXml(" + strDefName + ");"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" out.write(" + strDefName + ");"); + ctxt.generateJavaSource(" }else{"); + ctxt.generateBody(); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource("}"); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Param.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Param.java new file mode 100644 index 000000000..42256b869 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Param.java @@ -0,0 +1,77 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.TagPlugin; +import org.apache.jasper.compiler.tagplugin.TagPluginContext; + +public class Param implements TagPlugin { + + public void doTag(TagPluginContext ctxt) { + + //don't support the body content + + //define names of all the temp variables + String nameName = ctxt.getTemporaryVariableName(); + String valueName = ctxt.getTemporaryVariableName(); + String urlName = ctxt.getTemporaryVariableName(); + String encName = ctxt.getTemporaryVariableName(); + String index = ctxt.getTemporaryVariableName(); + + //if the param tag has no parents, throw a exception + TagPluginContext parent = ctxt.getParentContext(); + if(parent == null){ + ctxt.generateJavaSource(" throw new JspTagExcption" + + "(\"<param> outside <import> or <urlEncode>\");"); + return; + } + + //get the url string before adding this param + ctxt.generateJavaSource("String " + urlName + " = " + + "(String)pageContext.getAttribute(\"url_without_param\");"); + + //get the value of "name" + ctxt.generateJavaSource("String " + nameName + " = "); + ctxt.generateAttribute("name"); + ctxt.generateJavaSource(";"); + + //if the "name" is null then do nothing. + //else add such string "name=value" to the url. + //and the url should be encoded + ctxt.generateJavaSource("if(" + nameName + " != null && !" + nameName + ".equals(\"\")){"); + ctxt.generateJavaSource(" String " + valueName + " = "); + ctxt.generateAttribute("value"); + ctxt.generateJavaSource(";"); + ctxt.generateJavaSource(" if(" + valueName + " == null) " + valueName + " = \"\";"); + ctxt.generateJavaSource(" String " + encName + " = pageContext.getResponse().getCharacterEncoding();"); + ctxt.generateJavaSource(" " + nameName + " = java.net.URLEncoder.encode(" + nameName + ", " + encName + ");"); + ctxt.generateJavaSource(" " + valueName + " = java.net.URLEncoder.encode(" + valueName + ", " + encName + ");"); + ctxt.generateJavaSource(" int " + index + ";"); + ctxt.generateJavaSource(" " + index + " = " + urlName + ".indexOf(\'?\');"); + //if the current param is the first one, add a "?" ahead of it + //else add a "&" ahead of it + ctxt.generateJavaSource(" if(" + index + " == -1){"); + ctxt.generateJavaSource(" " + urlName + " = " + urlName + " + \"?\" + " + nameName + " + \"=\" + " + valueName + ";"); + ctxt.generateJavaSource(" }else{"); + ctxt.generateJavaSource(" " + urlName + " = " + urlName + " + \"&\" + " + nameName + " + \"=\" + " + valueName + ";"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" pageContext.setAttribute(\"url_without_param\"," + urlName + ");"); + ctxt.generateJavaSource("}"); + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Redirect.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Redirect.java new file mode 100644 index 000000000..6a5813fb8 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Redirect.java @@ -0,0 +1,83 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.TagPlugin; +import org.apache.jasper.compiler.tagplugin.TagPluginContext; + +public class Redirect implements TagPlugin { + + public void doTag(TagPluginContext ctxt) { + + //flag for the existence of the "context" + boolean hasContext = ctxt.isAttributeSpecified("context"); + + //names of the temp variables + String urlName = ctxt.getTemporaryVariableName(); + String contextName = ctxt.getTemporaryVariableName(); + String baseUrlName = ctxt.getTemporaryVariableName(); + String resultName = ctxt.getTemporaryVariableName(); + String responseName = ctxt.getTemporaryVariableName(); + + //get context + ctxt.generateJavaSource("String " + contextName + " = null;"); + if(hasContext){ + ctxt.generateJavaSource(contextName + " = "); + ctxt.generateAttribute("context"); + ctxt.generateJavaSource(";"); + } + + //get the url + ctxt.generateJavaSource("String " + urlName + " = "); + ctxt.generateAttribute("url"); + ctxt.generateJavaSource(";"); + + //get the raw url according to "url" and "context" + ctxt.generateJavaSource("String " + baseUrlName + " = " + + "org.apache.jasper.tagplugins.jstl.Util.resolveUrl(" + urlName + ", " + contextName + ", pageContext);"); + ctxt.generateJavaSource("pageContext.setAttribute" + + "(\"url_without_param\", " + baseUrlName + ");"); + + //add params + ctxt.generateBody(); + + ctxt.generateJavaSource("String " + resultName + " = " + + "(String)pageContext.getAttribute(\"url_without_param\");"); + ctxt.generateJavaSource("pageContext.removeAttribute" + + "(\"url_without_param\");"); + + //get the response object + ctxt.generateJavaSource("HttpServletResponse " + responseName + " = " + + "((HttpServletResponse) pageContext.getResponse());"); + + //if the url is relative, encode it + ctxt.generateJavaSource("if(!org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl(" + resultName + ")){"); + ctxt.generateJavaSource(" " + resultName + " = " + + responseName + ".encodeRedirectURL(" + resultName + ");"); + ctxt.generateJavaSource("}"); + + //do redirect + ctxt.generateJavaSource("try{"); + ctxt.generateJavaSource(" " + responseName + ".sendRedirect(" + resultName + ");"); + ctxt.generateJavaSource("}catch(java.io.IOException ex){"); + ctxt.generateJavaSource(" throw new JspTagException(ex.toString(), ex);"); + ctxt.generateJavaSource("}"); + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Remove.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Remove.java new file mode 100644 index 000000000..f21fa8f49 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Remove.java @@ -0,0 +1,45 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.TagPlugin; +import org.apache.jasper.compiler.tagplugin.TagPluginContext; +import org.apache.jasper.tagplugins.jstl.Util; + +public class Remove implements TagPlugin { + + public void doTag(TagPluginContext ctxt) { + + //scope flag + boolean hasScope = ctxt.isAttributeSpecified("scope"); + + //the value of the "var" + String strVar = ctxt.getConstantAttribute("var"); + + //remove attribute from certain scope. + //default scope is "page". + if(hasScope){ + int iScope = Util.getScope(ctxt.getConstantAttribute("scope")); + ctxt.generateJavaSource("pageContext.removeAttribute(\"" + strVar + "\"," + iScope + ");"); + }else{ + ctxt.generateJavaSource("pageContext.removeAttribute(\"" + strVar + "\");"); + } + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Set.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Set.java new file mode 100644 index 000000000..bfb2c8bf9 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Set.java @@ -0,0 +1,167 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.TagPlugin; +import org.apache.jasper.compiler.tagplugin.TagPluginContext; +import org.apache.jasper.tagplugins.jstl.Util; + +public class Set implements TagPlugin { + + public void doTag(TagPluginContext ctxt) { + + //the flags to indicate whether the attributes have been specified + boolean hasValue = false, hasVar = false, hasScope = false, + hasTarget = false; + + //the scope name + String strScope; + //the id of the scope + int iScope; + + //initialize the flags + hasValue = ctxt.isAttributeSpecified("value"); + hasVar = ctxt.isAttributeSpecified("var"); + hasScope = ctxt.isAttributeSpecified("scope"); + hasTarget = ctxt.isAttributeSpecified("target"); + + //the temp variables name + String resultName = ctxt.getTemporaryVariableName(); + String targetName = ctxt.getTemporaryVariableName(); + String propertyName = ctxt.getTemporaryVariableName(); + + //initialize the "result" which will be assigned to the var or target.property + ctxt.generateJavaSource("Object " + resultName + " = null;"); + if(hasValue){ + ctxt.generateJavaSource(resultName + " = "); + ctxt.generateAttribute("value"); + ctxt.generateJavaSource(";"); + }else{ + ctxt.dontUseTagPlugin(); + return; + } + + //initialize the strScope + if(hasScope){ + strScope = ctxt.getConstantAttribute("scope"); + }else{ + strScope = "page"; + } + + //get the iScope according to the strScope + iScope = Util.getScope(strScope); + + //if the attribute var has been specified then assign the result to the var; + if(hasVar){ + String strVar = ctxt.getConstantAttribute("var"); + ctxt.generateJavaSource("if(null != " + resultName + "){"); + ctxt.generateJavaSource(" pageContext.setAttribute(\"" + strVar + "\"," + resultName + "," + iScope + ");"); + ctxt.generateJavaSource("} else {"); + if(hasScope){ + ctxt.generateJavaSource(" pageContext.removeAttribute(\"" + strVar + "\"," + iScope + ");"); + }else{ + ctxt.generateJavaSource(" pageContext.removeAttribute(\"" + strVar + "\");"); + } + ctxt.generateJavaSource("}"); + + //else assign the result to the target.property + }else if(hasTarget){ + + //generate the temp variable name + String pdName = ctxt.getTemporaryVariableName(); + String successFlagName = ctxt.getTemporaryVariableName(); + String index = ctxt.getTemporaryVariableName(); + String methodName = ctxt.getTemporaryVariableName(); + + //initialize the property + ctxt.generateJavaSource("String " + propertyName + " = null;"); + ctxt.generateJavaSource("if("); + ctxt.generateAttribute("property"); + ctxt.generateJavaSource(" != null){"); + ctxt.generateJavaSource(" " + propertyName + " = ("); + ctxt.generateAttribute("property"); + ctxt.generateJavaSource(").toString();"); + ctxt.generateJavaSource("}"); + + //initialize the target + ctxt.generateJavaSource("Object " + targetName + " = "); + ctxt.generateAttribute("target"); + ctxt.generateJavaSource(";"); + + //the target is ok + ctxt.generateJavaSource("if(" + targetName + " != null){"); + + //if the target is a map, then put the result into the map with the key property + ctxt.generateJavaSource(" if(" + targetName + " instanceof java.util.Map){"); + ctxt.generateJavaSource(" if(null != " + resultName + "){"); + ctxt.generateJavaSource(" ((java.util.Map) " + targetName + ").put(" + propertyName + "," + resultName + ");"); + ctxt.generateJavaSource(" }else{"); + ctxt.generateJavaSource(" ((java.util.Map) " + targetName + ").remove(" + propertyName + ");"); + ctxt.generateJavaSource(" }"); + + //else assign the result to the target.property + ctxt.generateJavaSource(" }else{"); + ctxt.generateJavaSource(" try{"); + + //get all the property of the target + ctxt.generateJavaSource(" java.beans.PropertyDescriptor " + pdName + "[] = java.beans.Introspector.getBeanInfo(" + targetName + ".getClass()).getPropertyDescriptors();"); + + //the success flag is to imply whether the assign is successful + ctxt.generateJavaSource(" boolean " + successFlagName + " = false;"); + + //find the right property + ctxt.generateJavaSource(" for(int " + index + "=0;" + index + "<" + pdName + ".length;" + index + "++){"); + ctxt.generateJavaSource(" if(" + pdName + "[" + index + "].getName().equals(" + propertyName + ")){"); + + //get the "set" method; + ctxt.generateJavaSource(" java.lang.reflect.Method " + methodName + " = " + pdName + "[" + index + "].getWriteMethod();"); + ctxt.generateJavaSource(" if(null == " + methodName + "){"); + ctxt.generateJavaSource(" throw new JspException(\"No setter method in <set> for property \"+" + propertyName + ");"); + ctxt.generateJavaSource(" }"); + + //invoke the method through the reflection + ctxt.generateJavaSource(" if(" + resultName + " != null){"); + ctxt.generateJavaSource(" " + methodName + ".invoke(" + targetName + ", new Object[]{(" + methodName + ".getParameterTypes()[0]).cast(" + resultName + ")});"); + ctxt.generateJavaSource(" }else{"); + ctxt.generateJavaSource(" " + methodName + ".invoke(" + targetName + ", new Object[]{null});"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" " + successFlagName + " = true;"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" if(!" + successFlagName + "){"); + ctxt.generateJavaSource(" throw new JspException(\"Invalid property in <set>:\"+" + propertyName + ");"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" }"); + + //catch the el exception and throw it as a JspException + ctxt.generateJavaSource(" catch (IllegalAccessException ex) {"); + ctxt.generateJavaSource(" throw new JspException(ex);"); + ctxt.generateJavaSource(" } catch (java.beans.IntrospectionException ex) {"); + ctxt.generateJavaSource(" throw new JspException(ex);"); + ctxt.generateJavaSource(" } catch (java.lang.reflect.InvocationTargetException ex) {"); + ctxt.generateJavaSource(" throw new JspException(ex);"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource(" }"); + ctxt.generateJavaSource("}else{"); + ctxt.generateJavaSource(" throw new JspException();"); + ctxt.generateJavaSource("}"); + } + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Url.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Url.java new file mode 100644 index 000000000..9db64f64a --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Url.java @@ -0,0 +1,101 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.TagPlugin; +import org.apache.jasper.compiler.tagplugin.TagPluginContext; +import org.apache.jasper.tagplugins.jstl.Util; + +public class Url implements TagPlugin { + + public void doTag(TagPluginContext ctxt) { + + //flags + boolean hasVar, hasContext, hasScope; + + //init flags + hasVar = ctxt.isAttributeSpecified("var"); + hasContext = ctxt.isAttributeSpecified("context"); + hasScope = ctxt.isAttributeSpecified("scope"); + + //define name of the temp variables + String valueName = ctxt.getTemporaryVariableName(); + String contextName = ctxt.getTemporaryVariableName(); + String baseUrlName = ctxt.getTemporaryVariableName(); + String resultName = ctxt.getTemporaryVariableName(); + String responseName = ctxt.getTemporaryVariableName(); + + //get the scope + String strScope = "page"; + if(hasScope){ + strScope = ctxt.getConstantAttribute("scope"); + } + int iScope = Util.getScope(strScope); + + //get the value + ctxt.generateJavaSource("String " + valueName + " = "); + ctxt.generateAttribute("value"); + ctxt.generateJavaSource(";"); + + //get the context + ctxt.generateJavaSource("String " + contextName + " = null;"); + if(hasContext){ + ctxt.generateJavaSource(contextName + " = "); + ctxt.generateAttribute("context"); + ctxt.generateJavaSource(";"); + } + + //get the raw url + ctxt.generateJavaSource("String " + baseUrlName + " = " + + "org.apache.jasper.tagplugins.jstl.Util.resolveUrl(" + valueName + ", " + contextName + ", pageContext);"); + ctxt.generateJavaSource("pageContext.setAttribute" + + "(\"url_without_param\", " + baseUrlName + ");"); + + //add params + ctxt.generateBody(); + + ctxt.generateJavaSource("String " + resultName + " = " + + "(String)pageContext.getAttribute(\"url_without_param\");"); + ctxt.generateJavaSource("pageContext.removeAttribute(\"url_without_param\");"); + + //if the url is relative, encode it + ctxt.generateJavaSource("if(!org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl(" + resultName + ")){"); + ctxt.generateJavaSource(" HttpServletResponse " + responseName + " = " + + "((HttpServletResponse) pageContext.getResponse());"); + ctxt.generateJavaSource(" " + resultName + " = " + + responseName + ".encodeURL(" + resultName + ");"); + ctxt.generateJavaSource("}"); + + //if "var" is specified, the url string store in the attribute var defines + if(hasVar){ + String strVar = ctxt.getConstantAttribute("var"); + ctxt.generateJavaSource("pageContext.setAttribute" + + "(\"" + strVar + "\", " + resultName + ", " + iScope + ");"); + + //if var is not specified, just print out the url string + }else{ + ctxt.generateJavaSource("try{"); + ctxt.generateJavaSource(" pageContext.getOut().print(" + resultName + ");"); + ctxt.generateJavaSource("}catch(java.io.IOException ex){"); + ctxt.generateJavaSource(" throw new JspTagException(ex.toString(), ex);"); + ctxt.generateJavaSource("}"); + } + } + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/When.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/When.java new file mode 100644 index 000000000..10aefc8f1 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/When.java @@ -0,0 +1,50 @@ +/* + * 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.tagplugins.jstl.core; + +import org.apache.jasper.compiler.tagplugin.*; + +public final class When implements TagPlugin { + + public void doTag(TagPluginContext ctxt) { + // Get the parent context to determine if this is the first + TagPluginContext parentContext = ctxt.getParentContext(); + if (parentContext == null) { + ctxt.dontUseTagPlugin(); + return; + } + + if ("true".equals(parentContext.getPluginAttribute("hasBeenHere"))) { + ctxt.generateJavaSource("} else if("); + // See comment below for the reason we generate the extra "}" here. + } + else { + ctxt.generateJavaSource("if("); + parentContext.setPluginAttribute("hasBeenHere", "true"); + } + ctxt.generateAttribute("test"); + ctxt.generateJavaSource("){"); + ctxt.generateBody(); + + // We don't generate the closing "}" for the "if" here because there + // may be whitespaces in between 's. Instead we delay + // generating it until the next or or + // + } +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/tagPlugins.xml b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/tagPlugins.xml new file mode 100644 index 000000000..859c6c88d --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/tagPlugins.xml @@ -0,0 +1,63 @@ + + + + + org.apache.taglibs.standard.tag.rt.core.IfTag + org.apache.jasper.tagplugins.jstl.core.If + + + org.apache.taglibs.standard.tag.common.core.ChooseTag + org.apache.jasper.tagplugins.jstl.core.Choose + + + org.apache.taglibs.standard.tag.rt.core.WhenTag + org.apache.jasper.tagplugins.jstl.core.When + + + org.apache.taglibs.standard.tag.common.core.OtherwiseTag + org.apache.jasper.tagplugins.jstl.core.Otherwise + + + org.apache.taglibs.standard.tag.rt.core.ForEachTag + org.apache.jasper.tagplugins.jstl.core.ForEach + + + org.apache.taglibs.standard.tag.rt.core.OutTag + org.apache.jasper.tagplugins.jstl.core.Out + + + org.apache.taglibs.standard.tag.rt.core.SetTag + org.apache.jasper.tagplugins.jstl.core.Set + + + org.apache.taglibs.standard.tag.common.core.RemoveTag + org.apache.jasper.tagplugins.jstl.core.Remove + + + org.apache.taglibs.standard.tag.common.core.CatchTag + org.apache.jasper.tagplugins.jstl.core.Catch + + + org.apache.taglibs.standard.tag.rt.core.ForTokensTag + org.apache.jasper.tagplugins.jstl.core.ForTokens + + + org.apache.taglibs.standard.tag.rt.core.ImportTag + org.apache.jasper.tagplugins.jstl.core.Import + + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/Enumerator.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/Enumerator.java new file mode 100644 index 000000000..657e32dd0 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/Enumerator.java @@ -0,0 +1,176 @@ +/* + * 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.util; + + +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.NoSuchElementException; + + +/** + * Adapter class that wraps an Enumeration around a Java2 + * collection classes object Iterator so that existing APIs + * returning Enumerations can easily run on top of the new collections. + * Constructors are provided to easliy create such wrappers. + * + * @author Craig R. McClanahan + * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (Tue, 24 Oct 2006) $ + */ + +public final class Enumerator implements Enumeration { + + + // ----------------------------------------------------------- Constructors + + + /** + * Return an Enumeration over the values of the specified Collection. + * + * @param collection Collection whose values should be enumerated + */ + public Enumerator(Collection collection) { + + this(collection.iterator()); + + } + + + /** + * Return an Enumeration over the values of the specified Collection. + * + * @param collection Collection whose values should be enumerated + * @param clone true to clone iterator + */ + public Enumerator(Collection collection, boolean clone) { + + this(collection.iterator(), clone); + + } + + + /** + * Return an Enumeration over the values returned by the + * specified Iterator. + * + * @param iterator Iterator to be wrapped + */ + public Enumerator(Iterator iterator) { + + super(); + this.iterator = iterator; + + } + + + /** + * Return an Enumeration over the values returned by the + * specified Iterator. + * + * @param iterator Iterator to be wrapped + * @param clone true to clone iterator + */ + public Enumerator(Iterator iterator, boolean clone) { + + super(); + if (!clone) { + this.iterator = iterator; + } else { + List list = new ArrayList(); + while (iterator.hasNext()) { + list.add(iterator.next()); + } + this.iterator = list.iterator(); + } + + } + + + /** + * Return an Enumeration over the values of the specified Map. + * + * @param map Map whose values should be enumerated + */ + public Enumerator(Map map) { + + this(map.values().iterator()); + + } + + + /** + * Return an Enumeration over the values of the specified Map. + * + * @param map Map whose values should be enumerated + * @param clone true to clone iterator + */ + public Enumerator(Map map, boolean clone) { + + this(map.values().iterator(), clone); + + } + + + // ----------------------------------------------------- Instance Variables + + + /** + * The Iterator over which the Enumeration + * represented by this class actually operates. + */ + private Iterator iterator = null; + + + // --------------------------------------------------------- Public Methods + + + /** + * Tests if this enumeration contains more elements. + * + * @return true if and only if this enumeration object + * contains at least one more element to provide, false + * otherwise + */ + public boolean hasMoreElements() { + + return (iterator.hasNext()); + + } + + + /** + * Returns the next element of this enumeration if this enumeration + * has at least one more element to provide. + * + * @return the next element of this enumeration + * + * @exception NoSuchElementException if no more elements exist + */ + public Object nextElement() throws NoSuchElementException { + + return (iterator.next()); + + } + + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/ASCIIReader.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/ASCIIReader.java new file mode 100644 index 000000000..3cdcc8e70 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/ASCIIReader.java @@ -0,0 +1,204 @@ +/* + * 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.xmlparser; + +import java.io.InputStream; +import java.io.IOException; +import java.io.Reader; +import org.apache.jasper.compiler.Localizer; + +/** + * A simple ASCII byte reader. This is an optimized reader for reading + * byte streams that only contain 7-bit ASCII characters. + * + * @author Andy Clark, IBM + * + * @version $Id: ASCIIReader.java 708125 2008-10-27 10:10:13Z markt $ + */ +public class ASCIIReader + extends Reader { + + // + // Constants + // + + /** Default byte buffer size (2048). */ + public static final int DEFAULT_BUFFER_SIZE = 2048; + + // + // Data + // + + /** Input stream. */ + protected InputStream fInputStream; + + /** Byte buffer. */ + protected byte[] fBuffer; + + // + // Constructors + // + + /** + * Constructs an ASCII reader from the specified input stream + * and buffer size. + * + * @param inputStream The input stream. + * @param size The initial buffer size. + */ + public ASCIIReader(InputStream inputStream, int size) { + fInputStream = inputStream; + fBuffer = new byte[size]; + } + + // + // Reader methods + // + + /** + * Read a single character. This method will block until a character is + * available, an I/O error occurs, or the end of the stream is reached. + * + *

Subclasses that intend to support efficient single-character input + * should override this method. + * + * @return The character read, as an integer in the range 0 to 127 + * (0x00-0x7f), or -1 if the end of the stream has + * been reached + * + * @exception IOException If an I/O error occurs + */ + public int read() throws IOException { + int b0 = fInputStream.read(); + if (b0 > 0x80) { + throw new IOException(Localizer.getMessage("jsp.error.xml.invalidASCII", + Integer.toString(b0))); + } + return b0; + } // read():int + + /** + * Read characters into a portion of an array. This method will block + * until some input is available, an I/O error occurs, or the end of the + * stream is reached. + * + * @param ch Destination buffer + * @param offset Offset at which to start storing characters + * @param length Maximum number of characters to read + * + * @return The number of characters read, or -1 if the end of the + * stream has been reached + * + * @exception IOException If an I/O error occurs + */ + public int read(char ch[], int offset, int length) throws IOException { + if (length > fBuffer.length) { + length = fBuffer.length; + } + int count = fInputStream.read(fBuffer, 0, length); + for (int i = 0; i < count; i++) { + int b0 = (0xff & fBuffer[i]); // Convert to unsigned + if (b0 > 0x80) { + throw new IOException(Localizer.getMessage("jsp.error.xml.invalidASCII", + Integer.toString(b0))); + } + ch[offset + i] = (char)b0; + } + return count; + } // read(char[],int,int) + + /** + * Skip characters. This method will block until some characters are + * available, an I/O error occurs, or the end of the stream is reached. + * + * @param n The number of characters to skip + * + * @return The number of characters actually skipped + * + * @exception IOException If an I/O error occurs + */ + public long skip(long n) throws IOException { + return fInputStream.skip(n); + } // skip(long):long + + /** + * Tell whether this stream is ready to be read. + * + * @return True if the next read() is guaranteed not to block for input, + * false otherwise. Note that returning false does not guarantee that the + * next read will block. + * + * @exception IOException If an I/O error occurs + */ + public boolean ready() throws IOException { + return false; + } // ready() + + /** + * Tell whether this stream supports the mark() operation. + */ + public boolean markSupported() { + return fInputStream.markSupported(); + } // markSupported() + + /** + * Mark the present position in the stream. Subsequent calls to reset() + * will attempt to reposition the stream to this point. Not all + * character-input streams support the mark() operation. + * + * @param readAheadLimit Limit on the number of characters that may be + * read while still preserving the mark. After + * reading this many characters, attempting to + * reset the stream may fail. + * + * @exception IOException If the stream does not support mark(), + * or if some other I/O error occurs + */ + public void mark(int readAheadLimit) throws IOException { + fInputStream.mark(readAheadLimit); + } // mark(int) + + /** + * Reset the stream. If the stream has been marked, then attempt to + * reposition it at the mark. If the stream has not been marked, then + * attempt to reset it in some way appropriate to the particular stream, + * for example by repositioning it to its starting point. Not all + * character-input streams support the reset() operation, and some support + * reset() without supporting mark(). + * + * @exception IOException If the stream has not been marked, + * or if the mark has been invalidated, + * or if the stream does not support reset(), + * or if some other I/O error occurs + */ + public void reset() throws IOException { + fInputStream.reset(); + } // reset() + + /** + * Close the stream. Once a stream has been closed, further read(), + * ready(), mark(), or reset() invocations will throw an IOException. + * Closing a previously-closed stream, however, has no effect. + * + * @exception IOException If an I/O error occurs + */ + public void close() throws IOException { + fInputStream.close(); + } // close() + +} // class ASCIIReader diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/EncodingMap.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/EncodingMap.java new file mode 100644 index 000000000..c5bb061f3 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/EncodingMap.java @@ -0,0 +1,1020 @@ +/* + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 1999, International + * Business Machines, Inc., http://www.apache.org. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.jasper.xmlparser; + +import java.util.Hashtable; + +/** + * EncodingMap is a convenience class which handles conversions between + * IANA encoding names and Java encoding names, and vice versa. The + * encoding names used in XML instance documents must + * be the IANA encoding names specified or one of the aliases for those names + * which IANA defines. + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
+ *

Common Name + *

+ *

Use this name in XML files + *

+ *

Name Type + *

+ *

Xerces converts to this Java Encoder Name + *

8 bit Unicode + *

UTF-8 + *

+ *

IANA + *

+ *

UTF8 + *

ISO Latin 1 + *

ISO-8859-1 + *

+ *

MIME + *

+ *

ISO-8859-1 + *

ISO Latin 2 + *

ISO-8859-2 + *

+ *

MIME + *

+ *

ISO-8859-2 + *

ISO Latin 3 + *

ISO-8859-3 + *

+ *

MIME + *

+ *

ISO-8859-3 + *

ISO Latin 4 + *

ISO-8859-4 + *

+ *

MIME + *

+ *

ISO-8859-4 + *

ISO Latin Cyrillic + *

ISO-8859-5 + *

+ *

MIME + *

+ *

ISO-8859-5 + *

ISO Latin Arabic + *

ISO-8859-6 + *

+ *

MIME + *

+ *

ISO-8859-6 + *

ISO Latin Greek + *

ISO-8859-7 + *

+ *

MIME + *

+ *

ISO-8859-7 + *

ISO Latin Hebrew + *

ISO-8859-8 + *

+ *

MIME + *

+ *

ISO-8859-8 + *

ISO Latin 5 + *

ISO-8859-9 + *

+ *

MIME + *

+ *

ISO-8859-9 + *

EBCDIC: US + *

ebcdic-cp-us + *

+ *

IANA + *

+ *

cp037 + *

EBCDIC: Canada + *

ebcdic-cp-ca + *

+ *

IANA + *

+ *

cp037 + *

EBCDIC: Netherlands + *

ebcdic-cp-nl + *

+ *

IANA + *

+ *

cp037 + *

EBCDIC: Denmark + *

ebcdic-cp-dk + *

+ *

IANA + *

+ *

cp277 + *

EBCDIC: Norway + *

ebcdic-cp-no + *

+ *

IANA + *

+ *

cp277 + *

EBCDIC: Finland + *

ebcdic-cp-fi + *

+ *

IANA + *

+ *

cp278 + *

EBCDIC: Sweden + *

ebcdic-cp-se + *

+ *

IANA + *

+ *

cp278 + *

EBCDIC: Italy + *

ebcdic-cp-it + *

+ *

IANA + *

+ *

cp280 + *

EBCDIC: Spain, Latin America + *

ebcdic-cp-es + *

+ *

IANA + *

+ *

cp284 + *

EBCDIC: Great Britain + *

ebcdic-cp-gb + *

+ *

IANA + *

+ *

cp285 + *

EBCDIC: France + *

ebcdic-cp-fr + *

+ *

IANA + *

+ *

cp297 + *

EBCDIC: Arabic + *

ebcdic-cp-ar1 + *

+ *

IANA + *

+ *

cp420 + *

EBCDIC: Hebrew + *

ebcdic-cp-he + *

+ *

IANA + *

+ *

cp424 + *

EBCDIC: Switzerland + *

ebcdic-cp-ch + *

+ *

IANA + *

+ *

cp500 + *

EBCDIC: Roece + *

ebcdic-cp-roece + *

+ *

IANA + *

+ *

cp870 + *

EBCDIC: Yugoslavia + *

ebcdic-cp-yu + *

+ *

IANA + *

+ *

cp870 + *

EBCDIC: Iceland + *

ebcdic-cp-is + *

+ *

IANA + *

+ *

cp871 + *

EBCDIC: Urdu + *

ebcdic-cp-ar2 + *

+ *

IANA + *

+ *

cp918 + *

Chinese for PRC, mixed 1/2 byte + *

gb2312 + *

+ *

MIME + *

+ *

GB2312 + *

Extended Unix Code, packed for Japanese + *

euc-jp + *

+ *

MIME + *

+ *

eucjis + *

Japanese: iso-2022-jp + *

iso-2020-jp + *

+ *

MIME + *

+ *

JIS + *

Japanese: Shift JIS + *

Shift_JIS + *

+ *

MIME + *

+ *

SJIS + *

Chinese: Big5 + *

Big5 + *

+ *

MIME + *

+ *

Big5 + *

Extended Unix Code, packed for Korean + *

euc-kr + *

+ *

MIME + *

+ *

iso2022kr + *

Cyrillic + *

koi8-r + *

+ *

MIME + *

+ *

koi8-r + *

+ * + * @author TAMURA Kent, IBM + * @author Andy Clark, IBM + * + * @version $Id: EncodingMap.java 467222 2006-10-24 03:17:11Z markt $ + */ +public class EncodingMap { + + // + // Data + // + + /** fIANA2JavaMap */ + protected final static Hashtable fIANA2JavaMap = new Hashtable(); + + /** fJava2IANAMap */ + protected final static Hashtable fJava2IANAMap = new Hashtable(); + + // + // Static initialization + // + + static { + + // add IANA to Java encoding mappings. + fIANA2JavaMap.put("BIG5", "Big5"); + fIANA2JavaMap.put("CSBIG5", "Big5"); + fIANA2JavaMap.put("CP037", "CP037"); + fIANA2JavaMap.put("IBM037", "CP037"); + fIANA2JavaMap.put("CSIBM037", "CP037"); + fIANA2JavaMap.put("EBCDIC-CP-US", "CP037"); + fIANA2JavaMap.put("EBCDIC-CP-CA", "CP037"); + fIANA2JavaMap.put("EBCDIC-CP-NL", "CP037"); + fIANA2JavaMap.put("EBCDIC-CP-WT", "CP037"); + fIANA2JavaMap.put("IBM273", "CP273"); + fIANA2JavaMap.put("CP273", "CP273"); + fIANA2JavaMap.put("CSIBM273", "CP273"); + fIANA2JavaMap.put("IBM277", "CP277"); + fIANA2JavaMap.put("CP277", "CP277"); + fIANA2JavaMap.put("CSIBM277", "CP277"); + fIANA2JavaMap.put("EBCDIC-CP-DK", "CP277"); + fIANA2JavaMap.put("EBCDIC-CP-NO", "CP277"); + fIANA2JavaMap.put("IBM278", "CP278"); + fIANA2JavaMap.put("CP278", "CP278"); + fIANA2JavaMap.put("CSIBM278", "CP278"); + fIANA2JavaMap.put("EBCDIC-CP-FI", "CP278"); + fIANA2JavaMap.put("EBCDIC-CP-SE", "CP278"); + fIANA2JavaMap.put("IBM280", "CP280"); + fIANA2JavaMap.put("CP280", "CP280"); + fIANA2JavaMap.put("CSIBM280", "CP280"); + fIANA2JavaMap.put("EBCDIC-CP-IT", "CP280"); + fIANA2JavaMap.put("IBM284", "CP284"); + fIANA2JavaMap.put("CP284", "CP284"); + fIANA2JavaMap.put("CSIBM284", "CP284"); + fIANA2JavaMap.put("EBCDIC-CP-ES", "CP284"); + fIANA2JavaMap.put("EBCDIC-CP-GB", "CP285"); + fIANA2JavaMap.put("IBM285", "CP285"); + fIANA2JavaMap.put("CP285", "CP285"); + fIANA2JavaMap.put("CSIBM285", "CP285"); + fIANA2JavaMap.put("EBCDIC-JP-KANA", "CP290"); + fIANA2JavaMap.put("IBM290", "CP290"); + fIANA2JavaMap.put("CP290", "CP290"); + fIANA2JavaMap.put("CSIBM290", "CP290"); + fIANA2JavaMap.put("EBCDIC-CP-FR", "CP297"); + fIANA2JavaMap.put("IBM297", "CP297"); + fIANA2JavaMap.put("CP297", "CP297"); + fIANA2JavaMap.put("CSIBM297", "CP297"); + fIANA2JavaMap.put("EBCDIC-CP-AR1", "CP420"); + fIANA2JavaMap.put("IBM420", "CP420"); + fIANA2JavaMap.put("CP420", "CP420"); + fIANA2JavaMap.put("CSIBM420", "CP420"); + fIANA2JavaMap.put("EBCDIC-CP-HE", "CP424"); + fIANA2JavaMap.put("IBM424", "CP424"); + fIANA2JavaMap.put("CP424", "CP424"); + fIANA2JavaMap.put("CSIBM424", "CP424"); + fIANA2JavaMap.put("IBM437", "CP437"); + fIANA2JavaMap.put("437", "CP437"); + fIANA2JavaMap.put("CP437", "CP437"); + fIANA2JavaMap.put("CSPC8CODEPAGE437", "CP437"); + fIANA2JavaMap.put("EBCDIC-CP-CH", "CP500"); + fIANA2JavaMap.put("IBM500", "CP500"); + fIANA2JavaMap.put("CP500", "CP500"); + fIANA2JavaMap.put("CSIBM500", "CP500"); + fIANA2JavaMap.put("EBCDIC-CP-CH", "CP500"); + fIANA2JavaMap.put("EBCDIC-CP-BE", "CP500"); + fIANA2JavaMap.put("IBM775", "CP775"); + fIANA2JavaMap.put("CP775", "CP775"); + fIANA2JavaMap.put("CSPC775BALTIC", "CP775"); + fIANA2JavaMap.put("IBM850", "CP850"); + fIANA2JavaMap.put("850", "CP850"); + fIANA2JavaMap.put("CP850", "CP850"); + fIANA2JavaMap.put("CSPC850MULTILINGUAL", "CP850"); + fIANA2JavaMap.put("IBM852", "CP852"); + fIANA2JavaMap.put("852", "CP852"); + fIANA2JavaMap.put("CP852", "CP852"); + fIANA2JavaMap.put("CSPCP852", "CP852"); + fIANA2JavaMap.put("IBM855", "CP855"); + fIANA2JavaMap.put("855", "CP855"); + fIANA2JavaMap.put("CP855", "CP855"); + fIANA2JavaMap.put("CSIBM855", "CP855"); + fIANA2JavaMap.put("IBM857", "CP857"); + fIANA2JavaMap.put("857", "CP857"); + fIANA2JavaMap.put("CP857", "CP857"); + fIANA2JavaMap.put("CSIBM857", "CP857"); + fIANA2JavaMap.put("IBM00858", "CP858"); + fIANA2JavaMap.put("CP00858", "CP858"); + fIANA2JavaMap.put("CCSID00858", "CP858"); + fIANA2JavaMap.put("IBM860", "CP860"); + fIANA2JavaMap.put("860", "CP860"); + fIANA2JavaMap.put("CP860", "CP860"); + fIANA2JavaMap.put("CSIBM860", "CP860"); + fIANA2JavaMap.put("IBM861", "CP861"); + fIANA2JavaMap.put("861", "CP861"); + fIANA2JavaMap.put("CP861", "CP861"); + fIANA2JavaMap.put("CP-IS", "CP861"); + fIANA2JavaMap.put("CSIBM861", "CP861"); + fIANA2JavaMap.put("IBM862", "CP862"); + fIANA2JavaMap.put("862", "CP862"); + fIANA2JavaMap.put("CP862", "CP862"); + fIANA2JavaMap.put("CSPC862LATINHEBREW", "CP862"); + fIANA2JavaMap.put("IBM863", "CP863"); + fIANA2JavaMap.put("863", "CP863"); + fIANA2JavaMap.put("CP863", "CP863"); + fIANA2JavaMap.put("CSIBM863", "CP863"); + fIANA2JavaMap.put("IBM864", "CP864"); + fIANA2JavaMap.put("CP864", "CP864"); + fIANA2JavaMap.put("CSIBM864", "CP864"); + fIANA2JavaMap.put("IBM865", "CP865"); + fIANA2JavaMap.put("865", "CP865"); + fIANA2JavaMap.put("CP865", "CP865"); + fIANA2JavaMap.put("CSIBM865", "CP865"); + fIANA2JavaMap.put("IBM866", "CP866"); + fIANA2JavaMap.put("866", "CP866"); + fIANA2JavaMap.put("CP866", "CP866"); + fIANA2JavaMap.put("CSIBM866", "CP866"); + fIANA2JavaMap.put("IBM868", "CP868"); + fIANA2JavaMap.put("CP868", "CP868"); + fIANA2JavaMap.put("CSIBM868", "CP868"); + fIANA2JavaMap.put("CP-AR", "CP868"); + fIANA2JavaMap.put("IBM869", "CP869"); + fIANA2JavaMap.put("CP869", "CP869"); + fIANA2JavaMap.put("CSIBM869", "CP869"); + fIANA2JavaMap.put("CP-GR", "CP869"); + fIANA2JavaMap.put("IBM870", "CP870"); + fIANA2JavaMap.put("CP870", "CP870"); + fIANA2JavaMap.put("CSIBM870", "CP870"); + fIANA2JavaMap.put("EBCDIC-CP-ROECE", "CP870"); + fIANA2JavaMap.put("EBCDIC-CP-YU", "CP870"); + fIANA2JavaMap.put("IBM871", "CP871"); + fIANA2JavaMap.put("CP871", "CP871"); + fIANA2JavaMap.put("CSIBM871", "CP871"); + fIANA2JavaMap.put("EBCDIC-CP-IS", "CP871"); + fIANA2JavaMap.put("IBM918", "CP918"); + fIANA2JavaMap.put("CP918", "CP918"); + fIANA2JavaMap.put("CSIBM918", "CP918"); + fIANA2JavaMap.put("EBCDIC-CP-AR2", "CP918"); + fIANA2JavaMap.put("IBM00924", "CP924"); + fIANA2JavaMap.put("CP00924", "CP924"); + fIANA2JavaMap.put("CCSID00924", "CP924"); + // is this an error??? + fIANA2JavaMap.put("EBCDIC-LATIN9--EURO", "CP924"); + fIANA2JavaMap.put("IBM1026", "CP1026"); + fIANA2JavaMap.put("CP1026", "CP1026"); + fIANA2JavaMap.put("CSIBM1026", "CP1026"); + fIANA2JavaMap.put("IBM01140", "Cp1140"); + fIANA2JavaMap.put("CP01140", "Cp1140"); + fIANA2JavaMap.put("CCSID01140", "Cp1140"); + fIANA2JavaMap.put("IBM01141", "Cp1141"); + fIANA2JavaMap.put("CP01141", "Cp1141"); + fIANA2JavaMap.put("CCSID01141", "Cp1141"); + fIANA2JavaMap.put("IBM01142", "Cp1142"); + fIANA2JavaMap.put("CP01142", "Cp1142"); + fIANA2JavaMap.put("CCSID01142", "Cp1142"); + fIANA2JavaMap.put("IBM01143", "Cp1143"); + fIANA2JavaMap.put("CP01143", "Cp1143"); + fIANA2JavaMap.put("CCSID01143", "Cp1143"); + fIANA2JavaMap.put("IBM01144", "Cp1144"); + fIANA2JavaMap.put("CP01144", "Cp1144"); + fIANA2JavaMap.put("CCSID01144", "Cp1144"); + fIANA2JavaMap.put("IBM01145", "Cp1145"); + fIANA2JavaMap.put("CP01145", "Cp1145"); + fIANA2JavaMap.put("CCSID01145", "Cp1145"); + fIANA2JavaMap.put("IBM01146", "Cp1146"); + fIANA2JavaMap.put("CP01146", "Cp1146"); + fIANA2JavaMap.put("CCSID01146", "Cp1146"); + fIANA2JavaMap.put("IBM01147", "Cp1147"); + fIANA2JavaMap.put("CP01147", "Cp1147"); + fIANA2JavaMap.put("CCSID01147", "Cp1147"); + fIANA2JavaMap.put("IBM01148", "Cp1148"); + fIANA2JavaMap.put("CP01148", "Cp1148"); + fIANA2JavaMap.put("CCSID01148", "Cp1148"); + fIANA2JavaMap.put("IBM01149", "Cp1149"); + fIANA2JavaMap.put("CP01149", "Cp1149"); + fIANA2JavaMap.put("CCSID01149", "Cp1149"); + fIANA2JavaMap.put("EUC-JP", "EUCJIS"); + fIANA2JavaMap.put("CSEUCPKDFMTJAPANESE", "EUCJIS"); + fIANA2JavaMap.put("EXTENDED_UNIX_CODE_PACKED_FORMAT_FOR_JAPANESE", "EUCJIS"); + fIANA2JavaMap.put("EUC-KR", "KSC5601"); + fIANA2JavaMap.put("CSEUCKR", "KSC5601"); + fIANA2JavaMap.put("KS_C_5601-1987", "KS_C_5601-1987"); + fIANA2JavaMap.put("ISO-IR-149", "KS_C_5601-1987"); + fIANA2JavaMap.put("KS_C_5601-1989", "KS_C_5601-1987"); + fIANA2JavaMap.put("KSC_5601", "KS_C_5601-1987"); + fIANA2JavaMap.put("KOREAN", "KS_C_5601-1987"); + fIANA2JavaMap.put("CSKSC56011987", "KS_C_5601-1987"); + fIANA2JavaMap.put("GB2312", "GB2312"); + fIANA2JavaMap.put("CSGB2312", "GB2312"); + fIANA2JavaMap.put("ISO-2022-JP", "JIS"); + fIANA2JavaMap.put("CSISO2022JP", "JIS"); + fIANA2JavaMap.put("ISO-2022-KR", "ISO2022KR"); + fIANA2JavaMap.put("CSISO2022KR", "ISO2022KR"); + fIANA2JavaMap.put("ISO-2022-CN", "ISO2022CN"); + + fIANA2JavaMap.put("X0201", "JIS0201"); + fIANA2JavaMap.put("CSISO13JISC6220JP", "JIS0201"); + fIANA2JavaMap.put("X0208", "JIS0208"); + fIANA2JavaMap.put("ISO-IR-87", "JIS0208"); + fIANA2JavaMap.put("X0208dbiJIS_X0208-1983", "JIS0208"); + fIANA2JavaMap.put("CSISO87JISX0208", "JIS0208"); + fIANA2JavaMap.put("X0212", "JIS0212"); + fIANA2JavaMap.put("ISO-IR-159", "JIS0212"); + fIANA2JavaMap.put("CSISO159JISX02121990", "JIS0212"); + fIANA2JavaMap.put("GB18030", "GB18030"); + fIANA2JavaMap.put("GBK", "GBK"); + fIANA2JavaMap.put("CP936", "GBK"); + fIANA2JavaMap.put("MS936", "GBK"); + fIANA2JavaMap.put("WINDOWS-936", "GBK"); + fIANA2JavaMap.put("SHIFT_JIS", "SJIS"); + fIANA2JavaMap.put("CSSHIFTJIS", "SJIS"); + fIANA2JavaMap.put("MS_KANJI", "SJIS"); + fIANA2JavaMap.put("WINDOWS-31J", "MS932"); + fIANA2JavaMap.put("CSWINDOWS31J", "MS932"); + + // Add support for Cp1252 and its friends + fIANA2JavaMap.put("WINDOWS-1250", "Cp1250"); + fIANA2JavaMap.put("WINDOWS-1251", "Cp1251"); + fIANA2JavaMap.put("WINDOWS-1252", "Cp1252"); + fIANA2JavaMap.put("WINDOWS-1253", "Cp1253"); + fIANA2JavaMap.put("WINDOWS-1254", "Cp1254"); + fIANA2JavaMap.put("WINDOWS-1255", "Cp1255"); + fIANA2JavaMap.put("WINDOWS-1256", "Cp1256"); + fIANA2JavaMap.put("WINDOWS-1257", "Cp1257"); + fIANA2JavaMap.put("WINDOWS-1258", "Cp1258"); + fIANA2JavaMap.put("TIS-620", "TIS620"); + + fIANA2JavaMap.put("ISO-8859-1", "ISO8859_1"); + fIANA2JavaMap.put("ISO-IR-100", "ISO8859_1"); + fIANA2JavaMap.put("ISO_8859-1", "ISO8859_1"); + fIANA2JavaMap.put("LATIN1", "ISO8859_1"); + fIANA2JavaMap.put("CSISOLATIN1", "ISO8859_1"); + fIANA2JavaMap.put("L1", "ISO8859_1"); + fIANA2JavaMap.put("IBM819", "ISO8859_1"); + fIANA2JavaMap.put("CP819", "ISO8859_1"); + + fIANA2JavaMap.put("ISO-8859-2", "ISO8859_2"); + fIANA2JavaMap.put("ISO-IR-101", "ISO8859_2"); + fIANA2JavaMap.put("ISO_8859-2", "ISO8859_2"); + fIANA2JavaMap.put("LATIN2", "ISO8859_2"); + fIANA2JavaMap.put("CSISOLATIN2", "ISO8859_2"); + fIANA2JavaMap.put("L2", "ISO8859_2"); + + fIANA2JavaMap.put("ISO-8859-3", "ISO8859_3"); + fIANA2JavaMap.put("ISO-IR-109", "ISO8859_3"); + fIANA2JavaMap.put("ISO_8859-3", "ISO8859_3"); + fIANA2JavaMap.put("LATIN3", "ISO8859_3"); + fIANA2JavaMap.put("CSISOLATIN3", "ISO8859_3"); + fIANA2JavaMap.put("L3", "ISO8859_3"); + + fIANA2JavaMap.put("ISO-8859-4", "ISO8859_4"); + fIANA2JavaMap.put("ISO-IR-110", "ISO8859_4"); + fIANA2JavaMap.put("ISO_8859-4", "ISO8859_4"); + fIANA2JavaMap.put("LATIN4", "ISO8859_4"); + fIANA2JavaMap.put("CSISOLATIN4", "ISO8859_4"); + fIANA2JavaMap.put("L4", "ISO8859_4"); + + fIANA2JavaMap.put("ISO-8859-5", "ISO8859_5"); + fIANA2JavaMap.put("ISO-IR-144", "ISO8859_5"); + fIANA2JavaMap.put("ISO_8859-5", "ISO8859_5"); + fIANA2JavaMap.put("CYRILLIC", "ISO8859_5"); + fIANA2JavaMap.put("CSISOLATINCYRILLIC", "ISO8859_5"); + + fIANA2JavaMap.put("ISO-8859-6", "ISO8859_6"); + fIANA2JavaMap.put("ISO-IR-127", "ISO8859_6"); + fIANA2JavaMap.put("ISO_8859-6", "ISO8859_6"); + fIANA2JavaMap.put("ECMA-114", "ISO8859_6"); + fIANA2JavaMap.put("ASMO-708", "ISO8859_6"); + fIANA2JavaMap.put("ARABIC", "ISO8859_6"); + fIANA2JavaMap.put("CSISOLATINARABIC", "ISO8859_6"); + + fIANA2JavaMap.put("ISO-8859-7", "ISO8859_7"); + fIANA2JavaMap.put("ISO-IR-126", "ISO8859_7"); + fIANA2JavaMap.put("ISO_8859-7", "ISO8859_7"); + fIANA2JavaMap.put("ELOT_928", "ISO8859_7"); + fIANA2JavaMap.put("ECMA-118", "ISO8859_7"); + fIANA2JavaMap.put("GREEK", "ISO8859_7"); + fIANA2JavaMap.put("CSISOLATINGREEK", "ISO8859_7"); + fIANA2JavaMap.put("GREEK8", "ISO8859_7"); + + fIANA2JavaMap.put("ISO-8859-8", "ISO8859_8"); + fIANA2JavaMap.put("ISO-8859-8-I", "ISO8859_8"); // added since this encoding only differs w.r.t. presentation + fIANA2JavaMap.put("ISO-IR-138", "ISO8859_8"); + fIANA2JavaMap.put("ISO_8859-8", "ISO8859_8"); + fIANA2JavaMap.put("HEBREW", "ISO8859_8"); + fIANA2JavaMap.put("CSISOLATINHEBREW", "ISO8859_8"); + + fIANA2JavaMap.put("ISO-8859-9", "ISO8859_9"); + fIANA2JavaMap.put("ISO-IR-148", "ISO8859_9"); + fIANA2JavaMap.put("ISO_8859-9", "ISO8859_9"); + fIANA2JavaMap.put("LATIN5", "ISO8859_9"); + fIANA2JavaMap.put("CSISOLATIN5", "ISO8859_9"); + fIANA2JavaMap.put("L5", "ISO8859_9"); + + fIANA2JavaMap.put("ISO-8859-13", "ISO8859_13"); + + fIANA2JavaMap.put("ISO-8859-15", "ISO8859_15_FDIS"); + fIANA2JavaMap.put("ISO_8859-15", "ISO8859_15_FDIS"); + fIANA2JavaMap.put("LATIN-9", "ISO8859_15_FDIS"); + + fIANA2JavaMap.put("KOI8-R", "KOI8_R"); + fIANA2JavaMap.put("CSKOI8R", "KOI8_R"); + fIANA2JavaMap.put("US-ASCII", "ASCII"); + fIANA2JavaMap.put("ISO-IR-6", "ASCII"); + fIANA2JavaMap.put("ANSI_X3.4-1968", "ASCII"); + fIANA2JavaMap.put("ANSI_X3.4-1986", "ASCII"); + fIANA2JavaMap.put("ISO_646.IRV:1991", "ASCII"); + fIANA2JavaMap.put("ASCII", "ASCII"); + fIANA2JavaMap.put("CSASCII", "ASCII"); + fIANA2JavaMap.put("ISO646-US", "ASCII"); + fIANA2JavaMap.put("US", "ASCII"); + fIANA2JavaMap.put("IBM367", "ASCII"); + fIANA2JavaMap.put("CP367", "ASCII"); + fIANA2JavaMap.put("UTF-8", "UTF8"); + fIANA2JavaMap.put("UTF-16", "UTF-16"); + fIANA2JavaMap.put("UTF-16BE", "UnicodeBig"); + fIANA2JavaMap.put("UTF-16LE", "UnicodeLittle"); + + // support for 1047, as proposed to be added to the + // IANA registry in + // http://lists.w3.org/Archives/Public/ietf-charset/2002JulSep/0049.html + fIANA2JavaMap.put("IBM-1047", "Cp1047"); + fIANA2JavaMap.put("IBM1047", "Cp1047"); + fIANA2JavaMap.put("CP1047", "Cp1047"); + + // Adding new aliases as proposed in + // http://lists.w3.org/Archives/Public/ietf-charset/2002JulSep/0058.html + fIANA2JavaMap.put("IBM-37", "CP037"); + fIANA2JavaMap.put("IBM-273", "CP273"); + fIANA2JavaMap.put("IBM-277", "CP277"); + fIANA2JavaMap.put("IBM-278", "CP278"); + fIANA2JavaMap.put("IBM-280", "CP280"); + fIANA2JavaMap.put("IBM-284", "CP284"); + fIANA2JavaMap.put("IBM-285", "CP285"); + fIANA2JavaMap.put("IBM-290", "CP290"); + fIANA2JavaMap.put("IBM-297", "CP297"); + fIANA2JavaMap.put("IBM-420", "CP420"); + fIANA2JavaMap.put("IBM-424", "CP424"); + fIANA2JavaMap.put("IBM-437", "CP437"); + fIANA2JavaMap.put("IBM-500", "CP500"); + fIANA2JavaMap.put("IBM-775", "CP775"); + fIANA2JavaMap.put("IBM-850", "CP850"); + fIANA2JavaMap.put("IBM-852", "CP852"); + fIANA2JavaMap.put("IBM-855", "CP855"); + fIANA2JavaMap.put("IBM-857", "CP857"); + fIANA2JavaMap.put("IBM-858", "CP858"); + fIANA2JavaMap.put("IBM-860", "CP860"); + fIANA2JavaMap.put("IBM-861", "CP861"); + fIANA2JavaMap.put("IBM-862", "CP862"); + fIANA2JavaMap.put("IBM-863", "CP863"); + fIANA2JavaMap.put("IBM-864", "CP864"); + fIANA2JavaMap.put("IBM-865", "CP865"); + fIANA2JavaMap.put("IBM-866", "CP866"); + fIANA2JavaMap.put("IBM-868", "CP868"); + fIANA2JavaMap.put("IBM-869", "CP869"); + fIANA2JavaMap.put("IBM-870", "CP870"); + fIANA2JavaMap.put("IBM-871", "CP871"); + fIANA2JavaMap.put("IBM-918", "CP918"); + fIANA2JavaMap.put("IBM-924", "CP924"); + fIANA2JavaMap.put("IBM-1026", "CP1026"); + fIANA2JavaMap.put("IBM-1140", "Cp1140"); + fIANA2JavaMap.put("IBM-1141", "Cp1141"); + fIANA2JavaMap.put("IBM-1142", "Cp1142"); + fIANA2JavaMap.put("IBM-1143", "Cp1143"); + fIANA2JavaMap.put("IBM-1144", "Cp1144"); + fIANA2JavaMap.put("IBM-1145", "Cp1145"); + fIANA2JavaMap.put("IBM-1146", "Cp1146"); + fIANA2JavaMap.put("IBM-1147", "Cp1147"); + fIANA2JavaMap.put("IBM-1148", "Cp1148"); + fIANA2JavaMap.put("IBM-1149", "Cp1149"); + fIANA2JavaMap.put("IBM-819", "ISO8859_1"); + fIANA2JavaMap.put("IBM-367", "ASCII"); + + // REVISIT: + // j:CNS11643 -> EUC-TW? + // ISO-2022-CN? ISO-2022-CN-EXT? + + // add Java to IANA encoding mappings + //fJava2IANAMap.put("8859_1", "US-ASCII"); // ? + fJava2IANAMap.put("ISO8859_1", "ISO-8859-1"); + fJava2IANAMap.put("ISO8859_2", "ISO-8859-2"); + fJava2IANAMap.put("ISO8859_3", "ISO-8859-3"); + fJava2IANAMap.put("ISO8859_4", "ISO-8859-4"); + fJava2IANAMap.put("ISO8859_5", "ISO-8859-5"); + fJava2IANAMap.put("ISO8859_6", "ISO-8859-6"); + fJava2IANAMap.put("ISO8859_7", "ISO-8859-7"); + fJava2IANAMap.put("ISO8859_8", "ISO-8859-8"); + fJava2IANAMap.put("ISO8859_9", "ISO-8859-9"); + fJava2IANAMap.put("ISO8859_13", "ISO-8859-13"); + fJava2IANAMap.put("ISO8859_15", "ISO-8859-15"); + fJava2IANAMap.put("ISO8859_15_FDIS", "ISO-8859-15"); + fJava2IANAMap.put("Big5", "BIG5"); + fJava2IANAMap.put("CP037", "EBCDIC-CP-US"); + fJava2IANAMap.put("CP273", "IBM273"); + fJava2IANAMap.put("CP277", "EBCDIC-CP-DK"); + fJava2IANAMap.put("CP278", "EBCDIC-CP-FI"); + fJava2IANAMap.put("CP280", "EBCDIC-CP-IT"); + fJava2IANAMap.put("CP284", "EBCDIC-CP-ES"); + fJava2IANAMap.put("CP285", "EBCDIC-CP-GB"); + fJava2IANAMap.put("CP290", "EBCDIC-JP-KANA"); + fJava2IANAMap.put("CP297", "EBCDIC-CP-FR"); + fJava2IANAMap.put("CP420", "EBCDIC-CP-AR1"); + fJava2IANAMap.put("CP424", "EBCDIC-CP-HE"); + fJava2IANAMap.put("CP437", "IBM437"); + fJava2IANAMap.put("CP500", "EBCDIC-CP-CH"); + fJava2IANAMap.put("CP775", "IBM775"); + fJava2IANAMap.put("CP850", "IBM850"); + fJava2IANAMap.put("CP852", "IBM852"); + fJava2IANAMap.put("CP855", "IBM855"); + fJava2IANAMap.put("CP857", "IBM857"); + fJava2IANAMap.put("CP858", "IBM00858"); + fJava2IANAMap.put("CP860", "IBM860"); + fJava2IANAMap.put("CP861", "IBM861"); + fJava2IANAMap.put("CP862", "IBM862"); + fJava2IANAMap.put("CP863", "IBM863"); + fJava2IANAMap.put("CP864", "IBM864"); + fJava2IANAMap.put("CP865", "IBM865"); + fJava2IANAMap.put("CP866", "IBM866"); + fJava2IANAMap.put("CP868", "IBM868"); + fJava2IANAMap.put("CP869", "IBM869"); + fJava2IANAMap.put("CP870", "EBCDIC-CP-ROECE"); + fJava2IANAMap.put("CP871", "EBCDIC-CP-IS"); + fJava2IANAMap.put("CP918", "EBCDIC-CP-AR2"); + fJava2IANAMap.put("CP924", "IBM00924"); + fJava2IANAMap.put("CP1026", "IBM1026"); + fJava2IANAMap.put("Cp01140", "IBM01140"); + fJava2IANAMap.put("Cp01141", "IBM01141"); + fJava2IANAMap.put("Cp01142", "IBM01142"); + fJava2IANAMap.put("Cp01143", "IBM01143"); + fJava2IANAMap.put("Cp01144", "IBM01144"); + fJava2IANAMap.put("Cp01145", "IBM01145"); + fJava2IANAMap.put("Cp01146", "IBM01146"); + fJava2IANAMap.put("Cp01147", "IBM01147"); + fJava2IANAMap.put("Cp01148", "IBM01148"); + fJava2IANAMap.put("Cp01149", "IBM01149"); + fJava2IANAMap.put("EUCJIS", "EUC-JP"); + fJava2IANAMap.put("KS_C_5601-1987", "KS_C_5601-1987"); + fJava2IANAMap.put("GB2312", "GB2312"); + fJava2IANAMap.put("ISO2022KR", "ISO-2022-KR"); + fJava2IANAMap.put("ISO2022CN", "ISO-2022-CN"); + fJava2IANAMap.put("JIS", "ISO-2022-JP"); + fJava2IANAMap.put("KOI8_R", "KOI8-R"); + fJava2IANAMap.put("KSC5601", "EUC-KR"); + fJava2IANAMap.put("GB18030", "GB18030"); + fJava2IANAMap.put("GBK", "GBK"); + fJava2IANAMap.put("SJIS", "SHIFT_JIS"); + fJava2IANAMap.put("MS932", "WINDOWS-31J"); + fJava2IANAMap.put("UTF8", "UTF-8"); + fJava2IANAMap.put("Unicode", "UTF-16"); + fJava2IANAMap.put("UnicodeBig", "UTF-16BE"); + fJava2IANAMap.put("UnicodeLittle", "UTF-16LE"); + fJava2IANAMap.put("JIS0201", "X0201"); + fJava2IANAMap.put("JIS0208", "X0208"); + fJava2IANAMap.put("JIS0212", "ISO-IR-159"); + + // proposed addition (see above for details): + fJava2IANAMap.put("CP1047", "IBM1047"); + + } // () + + // + // Constructors + // + + /** Default constructor. */ + public EncodingMap() {} + + // + // Public static methods + // + + /** + * Adds an IANA to Java encoding name mapping. + * + * @param ianaEncoding The IANA encoding name. + * @param javaEncoding The Java encoding name. + */ + public static void putIANA2JavaMapping(String ianaEncoding, + String javaEncoding) { + fIANA2JavaMap.put(ianaEncoding, javaEncoding); + } // putIANA2JavaMapping(String,String) + + /** + * Returns the Java encoding name for the specified IANA encoding name. + * + * @param ianaEncoding The IANA encoding name. + */ + public static String getIANA2JavaMapping(String ianaEncoding) { + return (String)fIANA2JavaMap.get(ianaEncoding); + } // getIANA2JavaMapping(String):String + + /** + * Removes an IANA to Java encoding name mapping. + * + * @param ianaEncoding The IANA encoding name. + */ + public static String removeIANA2JavaMapping(String ianaEncoding) { + return (String)fIANA2JavaMap.remove(ianaEncoding); + } // removeIANA2JavaMapping(String):String + + /** + * Adds a Java to IANA encoding name mapping. + * + * @param javaEncoding The Java encoding name. + * @param ianaEncoding The IANA encoding name. + */ + public static void putJava2IANAMapping(String javaEncoding, + String ianaEncoding) { + fJava2IANAMap.put(javaEncoding, ianaEncoding); + } // putJava2IANAMapping(String,String) + + /** + * Returns the IANA encoding name for the specified Java encoding name. + * + * @param javaEncoding The Java encoding name. + */ + public static String getJava2IANAMapping(String javaEncoding) { + return (String)fJava2IANAMap.get(javaEncoding); + } // getJava2IANAMapping(String):String + + /** + * Removes a Java to IANA encoding name mapping. + * + * @param javaEncoding The Java encoding name. + */ + public static String removeJava2IANAMapping(String javaEncoding) { + return (String)fJava2IANAMap.remove(javaEncoding); + } // removeJava2IANAMapping + +} // class EncodingMap diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/ParserUtils.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/ParserUtils.java new file mode 100644 index 000000000..8b32a8708 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/ParserUtils.java @@ -0,0 +1,235 @@ +/* + * 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.xmlparser; + +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.jasper.Constants; +import org.apache.jasper.JasperException; +import org.apache.jasper.compiler.Localizer; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; +import org.w3c.dom.Comment; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + + +/** + * XML parsing utilities for processing web application deployment + * descriptor and tag library descriptor files. FIXME - make these + * use a separate class loader for the parser to be used. + * + * @author Craig R. McClanahan + * @version $Revision: 595799 $ $Date: 2007-11-16 21:02:12 +0100 (Fri, 16 Nov 2007) $ + */ + +public class ParserUtils { + + /** + * An error handler for use when parsing XML documents. + */ + static ErrorHandler errorHandler = new MyErrorHandler(); + + /** + * An entity resolver for use when parsing XML documents. + */ + static EntityResolver entityResolver = new MyEntityResolver(); + + // Turn off for JSP 2.0 until switch over to using xschema. + public static boolean validating = false; + + + // --------------------------------------------------------- Public Methods + + /** + * Parse the specified XML document, and return a TreeNode + * that corresponds to the root node of the document tree. + * + * @param uri URI of the XML document being parsed + * @param is Input source containing the deployment descriptor + * + * @exception JasperException if an input/output error occurs + * @exception JasperException if a parsing error occurs + */ + public TreeNode parseXMLDocument(String uri, InputSource is) + throws JasperException { + + Document document = null; + + // Perform an XML parse of this document, via JAXP + try { + DocumentBuilderFactory factory = + DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setValidating(validating); + DocumentBuilder builder = factory.newDocumentBuilder(); + builder.setEntityResolver(entityResolver); + builder.setErrorHandler(errorHandler); + document = builder.parse(is); + } catch (ParserConfigurationException ex) { + throw new JasperException + (Localizer.getMessage("jsp.error.parse.xml", uri), ex); + } catch (SAXParseException ex) { + throw new JasperException + (Localizer.getMessage("jsp.error.parse.xml.line", + uri, + Integer.toString(ex.getLineNumber()), + Integer.toString(ex.getColumnNumber())), + ex); + } catch (SAXException sx) { + throw new JasperException + (Localizer.getMessage("jsp.error.parse.xml", uri), sx); + } catch (IOException io) { + throw new JasperException + (Localizer.getMessage("jsp.error.parse.xml", uri), io); + } + + // Convert the resulting document to a graph of TreeNodes + return (convert(null, document.getDocumentElement())); + } + + + /** + * Parse the specified XML document, and return a TreeNode + * that corresponds to the root node of the document tree. + * + * @param uri URI of the XML document being parsed + * @param is Input stream containing the deployment descriptor + * + * @exception JasperException if an input/output error occurs + * @exception JasperException if a parsing error occurs + */ + public TreeNode parseXMLDocument(String uri, InputStream is) + throws JasperException { + + return (parseXMLDocument(uri, new InputSource(is))); + } + + + // ------------------------------------------------------ Protected Methods + + + /** + * Create and return a TreeNode that corresponds to the specified Node, + * including processing all of the attributes and children nodes. + * + * @param parent The parent TreeNode (if any) for the new TreeNode + * @param node The XML document Node to be converted + */ + protected TreeNode convert(TreeNode parent, Node node) { + + // Construct a new TreeNode for this node + TreeNode treeNode = new TreeNode(node.getNodeName(), parent); + + // Convert all attributes of this node + NamedNodeMap attributes = node.getAttributes(); + if (attributes != null) { + int n = attributes.getLength(); + for (int i = 0; i < n; i++) { + Node attribute = attributes.item(i); + treeNode.addAttribute(attribute.getNodeName(), + attribute.getNodeValue()); + } + } + + // Create and attach all children of this node + NodeList children = node.getChildNodes(); + if (children != null) { + int n = children.getLength(); + for (int i = 0; i < n; i++) { + Node child = children.item(i); + if (child instanceof Comment) + continue; + if (child instanceof Text) { + String body = ((Text) child).getData(); + if (body != null) { + body = body.trim(); + if (body.length() > 0) + treeNode.setBody(body); + } + } else { + TreeNode treeChild = convert(treeNode, child); + } + } + } + + // Return the completed TreeNode graph + return (treeNode); + } +} + + +// ------------------------------------------------------------ Private Classes + +class MyEntityResolver implements EntityResolver { + + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException { + for (int i = 0; i < Constants.CACHED_DTD_PUBLIC_IDS.length; i++) { + String cachedDtdPublicId = Constants.CACHED_DTD_PUBLIC_IDS[i]; + if (cachedDtdPublicId.equals(publicId)) { + String resourcePath = Constants.CACHED_DTD_RESOURCE_PATHS[i]; + InputStream input = this.getClass().getResourceAsStream( + resourcePath); + if (input == null) { + throw new SAXException(Localizer.getMessage( + "jsp.error.internal.filenotfound", resourcePath)); + } + InputSource isrc = new InputSource(input); + return isrc; + } + } + Log log = LogFactory.getLog(MyEntityResolver.class); + if (log.isDebugEnabled()) + log.debug("Resolve entity failed" + publicId + " " + systemId); + log.error(Localizer.getMessage("jsp.error.parse.xml.invalidPublicId", + publicId)); + return null; + } +} + +class MyErrorHandler implements ErrorHandler { + + public void warning(SAXParseException ex) throws SAXException { + Log log = LogFactory.getLog(MyErrorHandler.class); + if (log.isDebugEnabled()) + log.debug("ParserUtils: warning ", ex); + // We ignore warnings + } + + public void error(SAXParseException ex) throws SAXException { + throw ex; + } + + public void fatalError(SAXParseException ex) throws SAXException { + throw ex; + } +} \ No newline at end of file diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/SymbolTable.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/SymbolTable.java new file mode 100644 index 000000000..f849df358 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/SymbolTable.java @@ -0,0 +1,302 @@ +/* + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 1999, International + * Business Machines, Inc., http://www.apache.org. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.jasper.xmlparser; + +/** + * This class is a symbol table implementation that guarantees that + * strings used as identifiers are unique references. Multiple calls + * to addSymbol will always return the same string + * reference. + *

+ * The symbol table performs the same task as String.intern() + * with the following differences: + *

    + *
  • + * A new string object does not need to be created in order to + * retrieve a unique reference. Symbols can be added by using + * a series of characters in a character array. + *
  • + *
  • + * Users of the symbol table can provide their own symbol hashing + * implementation. For example, a simple string hashing algorithm + * may fail to produce a balanced set of hashcodes for symbols + * that are mostly unique. Strings with similar leading + * characters are especially prone to this poor hashing behavior. + *
  • + *
+ * + * @author Andy Clark + * @version $Id: SymbolTable.java 467222 2006-10-24 03:17:11Z markt $ + */ +public class SymbolTable { + + // + // Constants + // + + /** Default table size. */ + protected static final int TABLE_SIZE = 101; + + // + // Data + // + + /** Buckets. */ + protected Entry[] fBuckets = null; + + // actual table size + protected int fTableSize; + + // + // Constructors + // + + /** Constructs a symbol table with a default number of buckets. */ + public SymbolTable() { + this(TABLE_SIZE); + } + + /** Constructs a symbol table with a specified number of buckets. */ + public SymbolTable(int tableSize) { + fTableSize = tableSize; + fBuckets = new Entry[fTableSize]; + } + + // + // Public methods + // + + /** + * Adds the specified symbol to the symbol table and returns a + * reference to the unique symbol. If the symbol already exists, + * the previous symbol reference is returned instead, in order + * guarantee that symbol references remain unique. + * + * @param symbol The new symbol. + */ + public String addSymbol(String symbol) { + + // search for identical symbol + int bucket = hash(symbol) % fTableSize; + int length = symbol.length(); + OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) { + if (length == entry.characters.length) { + for (int i = 0; i < length; i++) { + if (symbol.charAt(i) != entry.characters[i]) { + continue OUTER; + } + } + return entry.symbol; + } + } + + // create new entry + Entry entry = new Entry(symbol, fBuckets[bucket]); + fBuckets[bucket] = entry; + return entry.symbol; + + } // addSymbol(String):String + + /** + * Adds the specified symbol to the symbol table and returns a + * reference to the unique symbol. If the symbol already exists, + * the previous symbol reference is returned instead, in order + * guarantee that symbol references remain unique. + * + * @param buffer The buffer containing the new symbol. + * @param offset The offset into the buffer of the new symbol. + * @param length The length of the new symbol in the buffer. + */ + public String addSymbol(char[] buffer, int offset, int length) { + + // search for identical symbol + int bucket = hash(buffer, offset, length) % fTableSize; + OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) { + if (length == entry.characters.length) { + for (int i = 0; i < length; i++) { + if (buffer[offset + i] != entry.characters[i]) { + continue OUTER; + } + } + return entry.symbol; + } + } + + // add new entry + Entry entry = new Entry(buffer, offset, length, fBuckets[bucket]); + fBuckets[bucket] = entry; + return entry.symbol; + + } // addSymbol(char[],int,int):String + + /** + * Returns a hashcode value for the specified symbol. The value + * returned by this method must be identical to the value returned + * by the hash(char[],int,int) method when called + * with the character array that comprises the symbol string. + * + * @param symbol The symbol to hash. + */ + public int hash(String symbol) { + + int code = 0; + int length = symbol.length(); + for (int i = 0; i < length; i++) { + code = code * 37 + symbol.charAt(i); + } + return code & 0x7FFFFFF; + + } // hash(String):int + + /** + * Returns a hashcode value for the specified symbol information. + * The value returned by this method must be identical to the value + * returned by the hash(String) method when called + * with the string object created from the symbol information. + * + * @param buffer The character buffer containing the symbol. + * @param offset The offset into the character buffer of the start + * of the symbol. + * @param length The length of the symbol. + */ + public int hash(char[] buffer, int offset, int length) { + + int code = 0; + for (int i = 0; i < length; i++) { + code = code * 37 + buffer[offset + i]; + } + return code & 0x7FFFFFF; + + } // hash(char[],int,int):int + + /** + * Returns true if the symbol table already contains the specified + * symbol. + * + * @param symbol The symbol to look for. + */ + public boolean containsSymbol(String symbol) { + + // search for identical symbol + int bucket = hash(symbol) % fTableSize; + int length = symbol.length(); + OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) { + if (length == entry.characters.length) { + for (int i = 0; i < length; i++) { + if (symbol.charAt(i) != entry.characters[i]) { + continue OUTER; + } + } + return true; + } + } + + return false; + + } // containsSymbol(String):boolean + + /** + * Returns true if the symbol table already contains the specified + * symbol. + * + * @param buffer The buffer containing the symbol to look for. + * @param offset The offset into the buffer. + * @param length The length of the symbol in the buffer. + */ + public boolean containsSymbol(char[] buffer, int offset, int length) { + + // search for identical symbol + int bucket = hash(buffer, offset, length) % fTableSize; + OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) { + if (length == entry.characters.length) { + for (int i = 0; i < length; i++) { + if (buffer[offset + i] != entry.characters[i]) { + continue OUTER; + } + } + return true; + } + } + + return false; + + } // containsSymbol(char[],int,int):boolean + + // + // Classes + // + + /** + * This class is a symbol table entry. Each entry acts as a node + * in a linked list. + */ + protected static final class Entry { + + // + // Data + // + + /** Symbol. */ + public String symbol; + + /** + * Symbol characters. This information is duplicated here for + * comparison performance. + */ + public char[] characters; + + /** The next entry. */ + public Entry next; + + // + // Constructors + // + + /** + * Constructs a new entry from the specified symbol and next entry + * reference. + */ + public Entry(String symbol, Entry next) { + this.symbol = symbol.intern(); + characters = new char[symbol.length()]; + symbol.getChars(0, characters.length, characters, 0); + this.next = next; + } + + /** + * Constructs a new entry from the specified symbol information and + * next entry reference. + */ + public Entry(char[] ch, int offset, int length, Entry next) { + characters = new char[length]; + System.arraycopy(ch, offset, characters, 0, length); + symbol = new String(characters).intern(); + this.next = next; + } + + } // class Entry + +} // class SymbolTable diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/TreeNode.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/TreeNode.java new file mode 100644 index 000000000..cfaa3b36f --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/TreeNode.java @@ -0,0 +1,360 @@ +/* + * 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.xmlparser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; + + +/** + * Simplified implementation of a Node from a Document Object Model (DOM) + * parse of an XML document. This class is used to represent a DOM tree + * so that the XML parser's implementation of org.w3c.dom need + * not be visible to the remainder of Jasper. + *

+ * WARNING - Construction of a new tree, or modifications + * to an existing one, are not thread-safe and such accesses must be + * synchronized. + * + * @author Craig R. McClanahan + * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (Tue, 24 Oct 2006) $ + */ + +public class TreeNode { + + + // ----------------------------------------------------------- Constructors + + + /** + * Construct a new node with no parent. + * + * @param name The name of this node + */ + public TreeNode(String name) { + + this(name, null); + + } + + + /** + * Construct a new node with the specified parent. + * + * @param name The name of this node + * @param parent The node that is the parent of this node + */ + public TreeNode(String name, TreeNode parent) { + + super(); + this.name = name; + this.parent = parent; + if (this.parent != null) + this.parent.addChild(this); + + } + + + // ----------------------------------------------------- Instance Variables + + + /** + * The attributes of this node, keyed by attribute name, + * Instantiated only if required. + */ + protected HashMap attributes = null; + + + /** + * The body text associated with this node (if any). + */ + protected String body = null; + + + /** + * The children of this node, instantiated only if required. + */ + protected ArrayList children = null; + + + /** + * The name of this node. + */ + protected String name = null; + + + /** + * The parent node of this node. + */ + protected TreeNode parent = null; + + + // --------------------------------------------------------- Public Methods + + + /** + * Add an attribute to this node, replacing any existing attribute + * with the same name. + * + * @param name The attribute name to add + * @param value The new attribute value + */ + public void addAttribute(String name, String value) { + + if (attributes == null) + attributes = new HashMap(); + attributes.put(name, value); + + } + + + /** + * Add a new child node to this node. + * + * @param node The new child node + */ + public void addChild(TreeNode node) { + + if (children == null) + children = new ArrayList(); + children.add(node); + + } + + + /** + * Return the value of the specified node attribute if it exists, or + * null otherwise. + * + * @param name Name of the requested attribute + */ + public String findAttribute(String name) { + + if (attributes == null) + return (null); + else + return ((String) attributes.get(name)); + + } + + + /** + * Return an Iterator of the attribute names of this node. If there are + * no attributes, an empty Iterator is returned. + */ + public Iterator findAttributes() { + + if (attributes == null) + return (Collections.EMPTY_LIST.iterator()); + else + return (attributes.keySet().iterator()); + + } + + + /** + * Return the first child node of this node with the specified name, + * if there is one; otherwise, return null. + * + * @param name Name of the desired child element + */ + public TreeNode findChild(String name) { + + if (children == null) + return (null); + Iterator items = children.iterator(); + while (items.hasNext()) { + TreeNode item = (TreeNode) items.next(); + if (name.equals(item.getName())) + return (item); + } + return (null); + + } + + + /** + * Return an Iterator of all children of this node. If there are no + * children, an empty Iterator is returned. + */ + public Iterator findChildren() { + + if (children == null) + return (Collections.EMPTY_LIST.iterator()); + else + return (children.iterator()); + + } + + + /** + * Return an Iterator over all children of this node that have the + * specified name. If there are no such children, an empty Iterator + * is returned. + * + * @param name Name used to select children + */ + public Iterator findChildren(String name) { + + if (children == null) + return (Collections.EMPTY_LIST.iterator()); + + ArrayList results = new ArrayList(); + Iterator items = children.iterator(); + while (items.hasNext()) { + TreeNode item = (TreeNode) items.next(); + if (name.equals(item.getName())) + results.add(item); + } + return (results.iterator()); + + } + + + /** + * Return the body text associated with this node (if any). + */ + public String getBody() { + + return (this.body); + + } + + + /** + * Return the name of this node. + */ + public String getName() { + + return (this.name); + + } + + + /** + * Remove any existing value for the specified attribute name. + * + * @param name The attribute name to remove + */ + public void removeAttribute(String name) { + + if (attributes != null) + attributes.remove(name); + + } + + + /** + * Remove a child node from this node, if it is one. + * + * @param node The child node to remove + */ + public void removeNode(TreeNode node) { + + if (children != null) + children.remove(node); + + } + + + /** + * Set the body text associated with this node (if any). + * + * @param body The body text (if any) + */ + public void setBody(String body) { + + this.body = body; + + } + + + /** + * Return a String representation of this TreeNode. + */ + public String toString() { + + StringBuffer sb = new StringBuffer(); + toString(sb, 0, this); + return (sb.toString()); + + } + + + // ------------------------------------------------------ Protected Methods + + + /** + * Append to the specified StringBuffer a character representation of + * this node, with the specified amount of indentation. + * + * @param sb The StringBuffer to append to + * @param indent Number of characters of indentation + * @param node The TreeNode to be printed + */ + protected void toString(StringBuffer sb, int indent, + TreeNode node) { + + int indent2 = indent + 2; + + // Reconstruct an opening node + for (int i = 0; i < indent; i++) + sb.append(' '); + sb.append('<'); + sb.append(node.getName()); + Iterator names = node.findAttributes(); + while (names.hasNext()) { + sb.append(' '); + String name = (String) names.next(); + sb.append(name); + sb.append("=\""); + String value = node.findAttribute(name); + sb.append(value); + sb.append("\""); + } + sb.append(">\n"); + + // Reconstruct the body text of this node (if any) + String body = node.getBody(); + if ((body != null) && (body.length() > 0)) { + for (int i = 0; i < indent2; i++) + sb.append(' '); + sb.append(body); + sb.append("\n"); + } + + // Reconstruct child nodes with extra indentation + Iterator children = node.findChildren(); + while (children.hasNext()) { + TreeNode child = (TreeNode) children.next(); + toString(sb, indent2, child); + } + + // Reconstruct a closing node marker + for (int i = 0; i < indent; i++) + sb.append(' '); + sb.append("\n"); + + } + + +} diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/UCSReader.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/UCSReader.java new file mode 100644 index 000000000..90498001a --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/UCSReader.java @@ -0,0 +1,301 @@ +/* + * 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.xmlparser; + +import java.io.InputStream; +import java.io.IOException; +import java.io.Reader; + +/** + * Reader for UCS-2 and UCS-4 encodings. + * (i.e., encodings from ISO-10646-UCS-(2|4)). + * + * @author Neil Graham, IBM + * + * @version $Id: UCSReader.java 467222 2006-10-24 03:17:11Z markt $ + */ +public class UCSReader extends Reader { + + private org.apache.juli.logging.Log log= + org.apache.juli.logging.LogFactory.getLog( UCSReader.class ); + + // + // Constants + // + + /** Default byte buffer size (8192, larger than that of ASCIIReader + * since it's reasonable to surmise that the average UCS-4-encoded + * file should be 4 times as large as the average ASCII-encoded file). + */ + public static final int DEFAULT_BUFFER_SIZE = 8192; + + public static final short UCS2LE = 1; + public static final short UCS2BE = 2; + public static final short UCS4LE = 4; + public static final short UCS4BE = 8; + + // + // Data + // + + /** Input stream. */ + protected InputStream fInputStream; + + /** Byte buffer. */ + protected byte[] fBuffer; + + // what kind of data we're dealing with + protected short fEncoding; + + // + // Constructors + // + + /** + * Constructs an ASCII reader from the specified input stream + * using the default buffer size. The Endian-ness and whether this is + * UCS-2 or UCS-4 needs also to be known in advance. + * + * @param inputStream The input stream. + * @param encoding One of UCS2LE, UCS2BE, UCS4LE or UCS4BE. + */ + public UCSReader(InputStream inputStream, short encoding) { + this(inputStream, DEFAULT_BUFFER_SIZE, encoding); + } // (InputStream, short) + + /** + * Constructs an ASCII reader from the specified input stream + * and buffer size. The Endian-ness and whether this is + * UCS-2 or UCS-4 needs also to be known in advance. + * + * @param inputStream The input stream. + * @param size The initial buffer size. + * @param encoding One of UCS2LE, UCS2BE, UCS4LE or UCS4BE. + */ + public UCSReader(InputStream inputStream, int size, short encoding) { + fInputStream = inputStream; + fBuffer = new byte[size]; + fEncoding = encoding; + } // (InputStream,int,short) + + // + // Reader methods + // + + /** + * Read a single character. This method will block until a character is + * available, an I/O error occurs, or the end of the stream is reached. + * + *

Subclasses that intend to support efficient single-character input + * should override this method. + * + * @return The character read, as an integer in the range 0 to 127 + * (0x00-0x7f), or -1 if the end of the stream has + * been reached + * + * @exception IOException If an I/O error occurs + */ + public int read() throws IOException { + int b0 = fInputStream.read() & 0xff; + if (b0 == 0xff) + return -1; + int b1 = fInputStream.read() & 0xff; + if (b1 == 0xff) + return -1; + if(fEncoding >=4) { + int b2 = fInputStream.read() & 0xff; + if (b2 == 0xff) + return -1; + int b3 = fInputStream.read() & 0xff; + if (b3 == 0xff) + return -1; + if (log.isDebugEnabled()) + log.debug("b0 is " + (b0 & 0xff) + " b1 " + (b1 & 0xff) + " b2 " + (b2 & 0xff) + " b3 " + (b3 & 0xff)); + if (fEncoding == UCS4BE) + return (b0<<24)+(b1<<16)+(b2<<8)+b3; + else + return (b3<<24)+(b2<<16)+(b1<<8)+b0; + } else { // UCS-2 + if (fEncoding == UCS2BE) + return (b0<<8)+b1; + else + return (b1<<8)+b0; + } + } // read():int + + /** + * Read characters into a portion of an array. This method will block + * until some input is available, an I/O error occurs, or the end of the + * stream is reached. + * + * @param ch Destination buffer + * @param offset Offset at which to start storing characters + * @param length Maximum number of characters to read + * + * @return The number of characters read, or -1 if the end of the + * stream has been reached + * + * @exception IOException If an I/O error occurs + */ + public int read(char ch[], int offset, int length) throws IOException { + int byteLength = length << ((fEncoding >= 4)?2:1); + if (byteLength > fBuffer.length) { + byteLength = fBuffer.length; + } + int count = fInputStream.read(fBuffer, 0, byteLength); + if(count == -1) return -1; + // try and make count be a multiple of the number of bytes we're looking for + if(fEncoding >= 4) { // BigEndian + // this looks ugly, but it avoids an if at any rate... + int numToRead = (4 - (count & 3) & 3); + for(int i=0; i> ((fEncoding >= 4)?2:1); + int curPos = 0; + for (int i = 0; i < numChars; i++) { + int b0 = fBuffer[curPos++] & 0xff; + int b1 = fBuffer[curPos++] & 0xff; + if(fEncoding >=4) { + int b2 = fBuffer[curPos++] & 0xff; + int b3 = fBuffer[curPos++] & 0xff; + if (fEncoding == UCS4BE) + ch[offset+i] = (char)((b0<<24)+(b1<<16)+(b2<<8)+b3); + else + ch[offset+i] = (char)((b3<<24)+(b2<<16)+(b1<<8)+b0); + } else { // UCS-2 + if (fEncoding == UCS2BE) + ch[offset+i] = (char)((b0<<8)+b1); + else + ch[offset+i] = (char)((b1<<8)+b0); + } + } + return numChars; + } // read(char[],int,int) + + /** + * Skip characters. This method will block until some characters are + * available, an I/O error occurs, or the end of the stream is reached. + * + * @param n The number of characters to skip + * + * @return The number of characters actually skipped + * + * @exception IOException If an I/O error occurs + */ + public long skip(long n) throws IOException { + // charWidth will represent the number of bits to move + // n leftward to get num of bytes to skip, and then move the result rightward + // to get num of chars effectively skipped. + // The trick with &'ing, as with elsewhere in this dcode, is + // intended to avoid an expensive use of / that might not be optimized + // away. + int charWidth = (fEncoding >=4)?2:1; + long bytesSkipped = fInputStream.skip(n<> charWidth; + return (bytesSkipped >> charWidth) + 1; + } // skip(long):long + + /** + * Tell whether this stream is ready to be read. + * + * @return True if the next read() is guaranteed not to block for input, + * false otherwise. Note that returning false does not guarantee that the + * next read will block. + * + * @exception IOException If an I/O error occurs + */ + public boolean ready() throws IOException { + return false; + } // ready() + + /** + * Tell whether this stream supports the mark() operation. + */ + public boolean markSupported() { + return fInputStream.markSupported(); + } // markSupported() + + /** + * Mark the present position in the stream. Subsequent calls to reset() + * will attempt to reposition the stream to this point. Not all + * character-input streams support the mark() operation. + * + * @param readAheadLimit Limit on the number of characters that may be + * read while still preserving the mark. After + * reading this many characters, attempting to + * reset the stream may fail. + * + * @exception IOException If the stream does not support mark(), + * or if some other I/O error occurs + */ + public void mark(int readAheadLimit) throws IOException { + fInputStream.mark(readAheadLimit); + } // mark(int) + + /** + * Reset the stream. If the stream has been marked, then attempt to + * reposition it at the mark. If the stream has not been marked, then + * attempt to reset it in some way appropriate to the particular stream, + * for example by repositioning it to its starting point. Not all + * character-input streams support the reset() operation, and some support + * reset() without supporting mark(). + * + * @exception IOException If the stream has not been marked, + * or if the mark has been invalidated, + * or if the stream does not support reset(), + * or if some other I/O error occurs + */ + public void reset() throws IOException { + fInputStream.reset(); + } // reset() + + /** + * Close the stream. Once a stream has been closed, further read(), + * ready(), mark(), or reset() invocations will throw an IOException. + * Closing a previously-closed stream, however, has no effect. + * + * @exception IOException If an I/O error occurs + */ + public void close() throws IOException { + fInputStream.close(); + } // close() + +} // class UCSReader diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/UTF8Reader.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/UTF8Reader.java new file mode 100644 index 000000000..9f50ed713 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/UTF8Reader.java @@ -0,0 +1,635 @@ +/* + * 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.xmlparser; + +import java.io.InputStream; +import java.io.IOException; +import java.io.Reader; +import java.io.UTFDataFormatException; +import org.apache.jasper.compiler.Localizer; + +/** + * @author Andy Clark, IBM + * + * @version $Id: UTF8Reader.java 467222 2006-10-24 03:17:11Z markt $ + */ +public class UTF8Reader + extends Reader { + + private org.apache.juli.logging.Log log= + org.apache.juli.logging.LogFactory.getLog( UTF8Reader.class ); + + // + // Constants + // + + /** Default byte buffer size (2048). */ + public static final int DEFAULT_BUFFER_SIZE = 2048; + + // debugging + + /** Debug read. */ + private static final boolean DEBUG_READ = false; + + // + // Data + // + + /** Input stream. */ + protected InputStream fInputStream; + + /** Byte buffer. */ + protected byte[] fBuffer; + + /** Offset into buffer. */ + protected int fOffset; + + /** Surrogate character. */ + private int fSurrogate = -1; + + // + // Constructors + // + + /** + * Constructs a UTF-8 reader from the specified input stream, + * buffer size and MessageFormatter. + * + * @param inputStream The input stream. + * @param size The initial buffer size. + */ + public UTF8Reader(InputStream inputStream, int size) { + fInputStream = inputStream; + fBuffer = new byte[size]; + } + + // + // Reader methods + // + + /** + * Read a single character. This method will block until a character is + * available, an I/O error occurs, or the end of the stream is reached. + * + *

Subclasses that intend to support efficient single-character input + * should override this method. + * + * @return The character read, as an integer in the range 0 to 16383 + * (0x00-0xffff), or -1 if the end of the stream has + * been reached + * + * @exception IOException If an I/O error occurs + */ + public int read() throws IOException { + + // decode character + int c = fSurrogate; + if (fSurrogate == -1) { + // NOTE: We use the index into the buffer if there are remaining + // bytes from the last block read. -Ac + int index = 0; + + // get first byte + int b0 = index == fOffset + ? fInputStream.read() : fBuffer[index++] & 0x00FF; + if (b0 == -1) { + return -1; + } + + // UTF-8: [0xxx xxxx] + // Unicode: [0000 0000] [0xxx xxxx] + if (b0 < 0x80) { + c = (char)b0; + } + + // UTF-8: [110y yyyy] [10xx xxxx] + // Unicode: [0000 0yyy] [yyxx xxxx] + else if ((b0 & 0xE0) == 0xC0) { + int b1 = index == fOffset + ? fInputStream.read() : fBuffer[index++] & 0x00FF; + if (b1 == -1) { + expectedByte(2, 2); + } + if ((b1 & 0xC0) != 0x80) { + invalidByte(2, 2, b1); + } + c = ((b0 << 6) & 0x07C0) | (b1 & 0x003F); + } + + // UTF-8: [1110 zzzz] [10yy yyyy] [10xx xxxx] + // Unicode: [zzzz yyyy] [yyxx xxxx] + else if ((b0 & 0xF0) == 0xE0) { + int b1 = index == fOffset + ? fInputStream.read() : fBuffer[index++] & 0x00FF; + if (b1 == -1) { + expectedByte(2, 3); + } + if ((b1 & 0xC0) != 0x80) { + invalidByte(2, 3, b1); + } + int b2 = index == fOffset + ? fInputStream.read() : fBuffer[index++] & 0x00FF; + if (b2 == -1) { + expectedByte(3, 3); + } + if ((b2 & 0xC0) != 0x80) { + invalidByte(3, 3, b2); + } + c = ((b0 << 12) & 0xF000) | ((b1 << 6) & 0x0FC0) | + (b2 & 0x003F); + } + + // UTF-8: [1111 0uuu] [10uu zzzz] [10yy yyyy] [10xx xxxx]* + // Unicode: [1101 10ww] [wwzz zzyy] (high surrogate) + // [1101 11yy] [yyxx xxxx] (low surrogate) + // * uuuuu = wwww + 1 + else if ((b0 & 0xF8) == 0xF0) { + int b1 = index == fOffset + ? fInputStream.read() : fBuffer[index++] & 0x00FF; + if (b1 == -1) { + expectedByte(2, 4); + } + if ((b1 & 0xC0) != 0x80) { + invalidByte(2, 3, b1); + } + int b2 = index == fOffset + ? fInputStream.read() : fBuffer[index++] & 0x00FF; + if (b2 == -1) { + expectedByte(3, 4); + } + if ((b2 & 0xC0) != 0x80) { + invalidByte(3, 3, b2); + } + int b3 = index == fOffset + ? fInputStream.read() : fBuffer[index++] & 0x00FF; + if (b3 == -1) { + expectedByte(4, 4); + } + if ((b3 & 0xC0) != 0x80) { + invalidByte(4, 4, b3); + } + int uuuuu = ((b0 << 2) & 0x001C) | ((b1 >> 4) & 0x0003); + if (uuuuu > 0x10) { + invalidSurrogate(uuuuu); + } + int wwww = uuuuu - 1; + int hs = 0xD800 | + ((wwww << 6) & 0x03C0) | ((b1 << 2) & 0x003C) | + ((b2 >> 4) & 0x0003); + int ls = 0xDC00 | ((b2 << 6) & 0x03C0) | (b3 & 0x003F); + c = hs; + fSurrogate = ls; + } + + // error + else { + invalidByte(1, 1, b0); + } + } + + // use surrogate + else { + fSurrogate = -1; + } + + // return character + if (DEBUG_READ) { + if (log.isDebugEnabled()) + log.debug("read(): 0x"+Integer.toHexString(c)); + } + return c; + + } // read():int + + /** + * Read characters into a portion of an array. This method will block + * until some input is available, an I/O error occurs, or the end of the + * stream is reached. + * + * @param ch Destination buffer + * @param offset Offset at which to start storing characters + * @param length Maximum number of characters to read + * + * @return The number of characters read, or -1 if the end of the + * stream has been reached + * + * @exception IOException If an I/O error occurs + */ + public int read(char ch[], int offset, int length) throws IOException { + + // handle surrogate + int out = offset; + if (fSurrogate != -1) { + ch[offset + 1] = (char)fSurrogate; + fSurrogate = -1; + length--; + out++; + } + + // read bytes + int count = 0; + if (fOffset == 0) { + // adjust length to read + if (length > fBuffer.length) { + length = fBuffer.length; + } + + // perform read operation + count = fInputStream.read(fBuffer, 0, length); + if (count == -1) { + return -1; + } + count += out - offset; + } + + // skip read; last character was in error + // NOTE: Having an offset value other than zero means that there was + // an error in the last character read. In this case, we have + // skipped the read so we don't consume any bytes past the + // error. By signalling the error on the next block read we + // allow the method to return the most valid characters that + // it can on the previous block read. -Ac + else { + count = fOffset; + fOffset = 0; + } + + // convert bytes to characters + final int total = count; + for (int in = 0; in < total; in++) { + int b0 = fBuffer[in] & 0x00FF; + + // UTF-8: [0xxx xxxx] + // Unicode: [0000 0000] [0xxx xxxx] + if (b0 < 0x80) { + ch[out++] = (char)b0; + continue; + } + + // UTF-8: [110y yyyy] [10xx xxxx] + // Unicode: [0000 0yyy] [yyxx xxxx] + if ((b0 & 0xE0) == 0xC0) { + int b1 = -1; + if (++in < total) { + b1 = fBuffer[in] & 0x00FF; + } + else { + b1 = fInputStream.read(); + if (b1 == -1) { + if (out > offset) { + fBuffer[0] = (byte)b0; + fOffset = 1; + return out - offset; + } + expectedByte(2, 2); + } + count++; + } + if ((b1 & 0xC0) != 0x80) { + if (out > offset) { + fBuffer[0] = (byte)b0; + fBuffer[1] = (byte)b1; + fOffset = 2; + return out - offset; + } + invalidByte(2, 2, b1); + } + int c = ((b0 << 6) & 0x07C0) | (b1 & 0x003F); + ch[out++] = (char)c; + count -= 1; + continue; + } + + // UTF-8: [1110 zzzz] [10yy yyyy] [10xx xxxx] + // Unicode: [zzzz yyyy] [yyxx xxxx] + if ((b0 & 0xF0) == 0xE0) { + int b1 = -1; + if (++in < total) { + b1 = fBuffer[in] & 0x00FF; + } + else { + b1 = fInputStream.read(); + if (b1 == -1) { + if (out > offset) { + fBuffer[0] = (byte)b0; + fOffset = 1; + return out - offset; + } + expectedByte(2, 3); + } + count++; + } + if ((b1 & 0xC0) != 0x80) { + if (out > offset) { + fBuffer[0] = (byte)b0; + fBuffer[1] = (byte)b1; + fOffset = 2; + return out - offset; + } + invalidByte(2, 3, b1); + } + int b2 = -1; + if (++in < total) { + b2 = fBuffer[in] & 0x00FF; + } + else { + b2 = fInputStream.read(); + if (b2 == -1) { + if (out > offset) { + fBuffer[0] = (byte)b0; + fBuffer[1] = (byte)b1; + fOffset = 2; + return out - offset; + } + expectedByte(3, 3); + } + count++; + } + if ((b2 & 0xC0) != 0x80) { + if (out > offset) { + fBuffer[0] = (byte)b0; + fBuffer[1] = (byte)b1; + fBuffer[2] = (byte)b2; + fOffset = 3; + return out - offset; + } + invalidByte(3, 3, b2); + } + int c = ((b0 << 12) & 0xF000) | ((b1 << 6) & 0x0FC0) | + (b2 & 0x003F); + ch[out++] = (char)c; + count -= 2; + continue; + } + + // UTF-8: [1111 0uuu] [10uu zzzz] [10yy yyyy] [10xx xxxx]* + // Unicode: [1101 10ww] [wwzz zzyy] (high surrogate) + // [1101 11yy] [yyxx xxxx] (low surrogate) + // * uuuuu = wwww + 1 + if ((b0 & 0xF8) == 0xF0) { + int b1 = -1; + if (++in < total) { + b1 = fBuffer[in] & 0x00FF; + } + else { + b1 = fInputStream.read(); + if (b1 == -1) { + if (out > offset) { + fBuffer[0] = (byte)b0; + fOffset = 1; + return out - offset; + } + expectedByte(2, 4); + } + count++; + } + if ((b1 & 0xC0) != 0x80) { + if (out > offset) { + fBuffer[0] = (byte)b0; + fBuffer[1] = (byte)b1; + fOffset = 2; + return out - offset; + } + invalidByte(2, 4, b1); + } + int b2 = -1; + if (++in < total) { + b2 = fBuffer[in] & 0x00FF; + } + else { + b2 = fInputStream.read(); + if (b2 == -1) { + if (out > offset) { + fBuffer[0] = (byte)b0; + fBuffer[1] = (byte)b1; + fOffset = 2; + return out - offset; + } + expectedByte(3, 4); + } + count++; + } + if ((b2 & 0xC0) != 0x80) { + if (out > offset) { + fBuffer[0] = (byte)b0; + fBuffer[1] = (byte)b1; + fBuffer[2] = (byte)b2; + fOffset = 3; + return out - offset; + } + invalidByte(3, 4, b2); + } + int b3 = -1; + if (++in < total) { + b3 = fBuffer[in] & 0x00FF; + } + else { + b3 = fInputStream.read(); + if (b3 == -1) { + if (out > offset) { + fBuffer[0] = (byte)b0; + fBuffer[1] = (byte)b1; + fBuffer[2] = (byte)b2; + fOffset = 3; + return out - offset; + } + expectedByte(4, 4); + } + count++; + } + if ((b3 & 0xC0) != 0x80) { + if (out > offset) { + fBuffer[0] = (byte)b0; + fBuffer[1] = (byte)b1; + fBuffer[2] = (byte)b2; + fBuffer[3] = (byte)b3; + fOffset = 4; + return out - offset; + } + invalidByte(4, 4, b2); + } + + // decode bytes into surrogate characters + int uuuuu = ((b0 << 2) & 0x001C) | ((b1 >> 4) & 0x0003); + if (uuuuu > 0x10) { + invalidSurrogate(uuuuu); + } + int wwww = uuuuu - 1; + int zzzz = b1 & 0x000F; + int yyyyyy = b2 & 0x003F; + int xxxxxx = b3 & 0x003F; + int hs = 0xD800 | ((wwww << 6) & 0x03C0) | (zzzz << 2) | (yyyyyy >> 4); + int ls = 0xDC00 | ((yyyyyy << 6) & 0x03C0) | xxxxxx; + + // set characters + ch[out++] = (char)hs; + ch[out++] = (char)ls; + count -= 2; + continue; + } + + // error + if (out > offset) { + fBuffer[0] = (byte)b0; + fOffset = 1; + return out - offset; + } + invalidByte(1, 1, b0); + } + + // return number of characters converted + if (DEBUG_READ) { + if (log.isDebugEnabled()) + log.debug("read(char[],"+offset+','+length+"): count="+count); + } + return count; + + } // read(char[],int,int) + + /** + * Skip characters. This method will block until some characters are + * available, an I/O error occurs, or the end of the stream is reached. + * + * @param n The number of characters to skip + * + * @return The number of characters actually skipped + * + * @exception IOException If an I/O error occurs + */ + public long skip(long n) throws IOException { + + long remaining = n; + final char[] ch = new char[fBuffer.length]; + do { + int length = ch.length < remaining ? ch.length : (int)remaining; + int count = read(ch, 0, length); + if (count > 0) { + remaining -= count; + } + else { + break; + } + } while (remaining > 0); + + long skipped = n - remaining; + return skipped; + + } // skip(long):long + + /** + * Tell whether this stream is ready to be read. + * + * @return True if the next read() is guaranteed not to block for input, + * false otherwise. Note that returning false does not guarantee that the + * next read will block. + * + * @exception IOException If an I/O error occurs + */ + public boolean ready() throws IOException { + return false; + } // ready() + + /** + * Tell whether this stream supports the mark() operation. + */ + public boolean markSupported() { + return false; + } // markSupported() + + /** + * Mark the present position in the stream. Subsequent calls to reset() + * will attempt to reposition the stream to this point. Not all + * character-input streams support the mark() operation. + * + * @param readAheadLimit Limit on the number of characters that may be + * read while still preserving the mark. After + * reading this many characters, attempting to + * reset the stream may fail. + * + * @exception IOException If the stream does not support mark(), + * or if some other I/O error occurs + */ + public void mark(int readAheadLimit) throws IOException { + throw new IOException( + Localizer.getMessage("jsp.error.xml.operationNotSupported", + "mark()", "UTF-8")); + } + + /** + * Reset the stream. If the stream has been marked, then attempt to + * reposition it at the mark. If the stream has not been marked, then + * attempt to reset it in some way appropriate to the particular stream, + * for example by repositioning it to its starting point. Not all + * character-input streams support the reset() operation, and some support + * reset() without supporting mark(). + * + * @exception IOException If the stream has not been marked, + * or if the mark has been invalidated, + * or if the stream does not support reset(), + * or if some other I/O error occurs + */ + public void reset() throws IOException { + fOffset = 0; + fSurrogate = -1; + } // reset() + + /** + * Close the stream. Once a stream has been closed, further read(), + * ready(), mark(), or reset() invocations will throw an IOException. + * Closing a previously-closed stream, however, has no effect. + * + * @exception IOException If an I/O error occurs + */ + public void close() throws IOException { + fInputStream.close(); + } // close() + + // + // Private methods + // + + /** Throws an exception for expected byte. */ + private void expectedByte(int position, int count) + throws UTFDataFormatException { + + throw new UTFDataFormatException( + Localizer.getMessage("jsp.error.xml.expectedByte", + Integer.toString(position), + Integer.toString(count))); + + } // expectedByte(int,int,int) + + /** Throws an exception for invalid byte. */ + private void invalidByte(int position, int count, int c) + throws UTFDataFormatException { + + throw new UTFDataFormatException( + Localizer.getMessage("jsp.error.xml.invalidByte", + Integer.toString(position), + Integer.toString(count))); + } // invalidByte(int,int,int,int) + + /** Throws an exception for invalid surrogate bits. */ + private void invalidSurrogate(int uuuuu) throws UTFDataFormatException { + + throw new UTFDataFormatException( + Localizer.getMessage("jsp.error.xml.invalidHighSurrogate", + Integer.toHexString(uuuuu))); + } // invalidSurrogate(int) + +} // class UTF8Reader diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLChar.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLChar.java new file mode 100644 index 000000000..16f34a40e --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLChar.java @@ -0,0 +1,1031 @@ +/* + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 1999, International + * Business Machines, Inc., http://www.apache.org. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.jasper.xmlparser; + +import java.util.Arrays; + +/** + * This class defines the basic XML character properties. The data + * in this class can be used to verify that a character is a valid + * XML character or if the character is a space, name start, or name + * character. + *

+ * A series of convenience methods are supplied to ease the burden + * of the developer. Because inlining the checks can improve per + * character performance, the tables of character properties are + * public. Using the character as an index into the CHARS + * array and applying the appropriate mask flag (e.g. + * MASK_VALID), yields the same results as calling the + * convenience methods. There is one exception: check the comments + * for the isValid method for details. + * + * @author Glenn Marcy, IBM + * @author Andy Clark, IBM + * @author Eric Ye, IBM + * @author Arnaud Le Hors, IBM + * @author Michael Glavassevich, IBM + * @author Rahul Srivastava, Sun Microsystems Inc. + * + * @version $Id: XMLChar.java 467222 2006-10-24 03:17:11Z markt $ + */ +public class XMLChar { + + // + // Constants + // + + /** Character flags. */ + private static final byte[] CHARS = new byte[1 << 16]; + + /** Valid character mask. */ + public static final int MASK_VALID = 0x01; + + /** Space character mask. */ + public static final int MASK_SPACE = 0x02; + + /** Name start character mask. */ + public static final int MASK_NAME_START = 0x04; + + /** Name character mask. */ + public static final int MASK_NAME = 0x08; + + /** Pubid character mask. */ + public static final int MASK_PUBID = 0x10; + + /** + * Content character mask. Special characters are those that can + * be considered the start of markup, such as '<' and '&'. + * The various newline characters are considered special as well. + * All other valid XML characters can be considered content. + *

+ * This is an optimization for the inner loop of character scanning. + */ + public static final int MASK_CONTENT = 0x20; + + /** NCName start character mask. */ + public static final int MASK_NCNAME_START = 0x40; + + /** NCName character mask. */ + public static final int MASK_NCNAME = 0x80; + + // + // Static initialization + // + + static { + + // Initializing the Character Flag Array + // Code generated by: XMLCharGenerator. + + CHARS[9] = 35; + CHARS[10] = 19; + CHARS[13] = 19; + CHARS[32] = 51; + CHARS[33] = 49; + CHARS[34] = 33; + Arrays.fill(CHARS, 35, 38, (byte) 49 ); // Fill 3 of value (byte) 49 + CHARS[38] = 1; + Arrays.fill(CHARS, 39, 45, (byte) 49 ); // Fill 6 of value (byte) 49 + Arrays.fill(CHARS, 45, 47, (byte) -71 ); // Fill 2 of value (byte) -71 + CHARS[47] = 49; + Arrays.fill(CHARS, 48, 58, (byte) -71 ); // Fill 10 of value (byte) -71 + CHARS[58] = 61; + CHARS[59] = 49; + CHARS[60] = 1; + CHARS[61] = 49; + CHARS[62] = 33; + Arrays.fill(CHARS, 63, 65, (byte) 49 ); // Fill 2 of value (byte) 49 + Arrays.fill(CHARS, 65, 91, (byte) -3 ); // Fill 26 of value (byte) -3 + Arrays.fill(CHARS, 91, 93, (byte) 33 ); // Fill 2 of value (byte) 33 + CHARS[93] = 1; + CHARS[94] = 33; + CHARS[95] = -3; + CHARS[96] = 33; + Arrays.fill(CHARS, 97, 123, (byte) -3 ); // Fill 26 of value (byte) -3 + Arrays.fill(CHARS, 123, 183, (byte) 33 ); // Fill 60 of value (byte) 33 + CHARS[183] = -87; + Arrays.fill(CHARS, 184, 192, (byte) 33 ); // Fill 8 of value (byte) 33 + Arrays.fill(CHARS, 192, 215, (byte) -19 ); // Fill 23 of value (byte) -19 + CHARS[215] = 33; + Arrays.fill(CHARS, 216, 247, (byte) -19 ); // Fill 31 of value (byte) -19 + CHARS[247] = 33; + Arrays.fill(CHARS, 248, 306, (byte) -19 ); // Fill 58 of value (byte) -19 + Arrays.fill(CHARS, 306, 308, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 308, 319, (byte) -19 ); // Fill 11 of value (byte) -19 + Arrays.fill(CHARS, 319, 321, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 321, 329, (byte) -19 ); // Fill 8 of value (byte) -19 + CHARS[329] = 33; + Arrays.fill(CHARS, 330, 383, (byte) -19 ); // Fill 53 of value (byte) -19 + CHARS[383] = 33; + Arrays.fill(CHARS, 384, 452, (byte) -19 ); // Fill 68 of value (byte) -19 + Arrays.fill(CHARS, 452, 461, (byte) 33 ); // Fill 9 of value (byte) 33 + Arrays.fill(CHARS, 461, 497, (byte) -19 ); // Fill 36 of value (byte) -19 + Arrays.fill(CHARS, 497, 500, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 500, 502, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 502, 506, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 506, 536, (byte) -19 ); // Fill 30 of value (byte) -19 + Arrays.fill(CHARS, 536, 592, (byte) 33 ); // Fill 56 of value (byte) 33 + Arrays.fill(CHARS, 592, 681, (byte) -19 ); // Fill 89 of value (byte) -19 + Arrays.fill(CHARS, 681, 699, (byte) 33 ); // Fill 18 of value (byte) 33 + Arrays.fill(CHARS, 699, 706, (byte) -19 ); // Fill 7 of value (byte) -19 + Arrays.fill(CHARS, 706, 720, (byte) 33 ); // Fill 14 of value (byte) 33 + Arrays.fill(CHARS, 720, 722, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 722, 768, (byte) 33 ); // Fill 46 of value (byte) 33 + Arrays.fill(CHARS, 768, 838, (byte) -87 ); // Fill 70 of value (byte) -87 + Arrays.fill(CHARS, 838, 864, (byte) 33 ); // Fill 26 of value (byte) 33 + Arrays.fill(CHARS, 864, 866, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 866, 902, (byte) 33 ); // Fill 36 of value (byte) 33 + CHARS[902] = -19; + CHARS[903] = -87; + Arrays.fill(CHARS, 904, 907, (byte) -19 ); // Fill 3 of value (byte) -19 + CHARS[907] = 33; + CHARS[908] = -19; + CHARS[909] = 33; + Arrays.fill(CHARS, 910, 930, (byte) -19 ); // Fill 20 of value (byte) -19 + CHARS[930] = 33; + Arrays.fill(CHARS, 931, 975, (byte) -19 ); // Fill 44 of value (byte) -19 + CHARS[975] = 33; + Arrays.fill(CHARS, 976, 983, (byte) -19 ); // Fill 7 of value (byte) -19 + Arrays.fill(CHARS, 983, 986, (byte) 33 ); // Fill 3 of value (byte) 33 + CHARS[986] = -19; + CHARS[987] = 33; + CHARS[988] = -19; + CHARS[989] = 33; + CHARS[990] = -19; + CHARS[991] = 33; + CHARS[992] = -19; + CHARS[993] = 33; + Arrays.fill(CHARS, 994, 1012, (byte) -19 ); // Fill 18 of value (byte) -19 + Arrays.fill(CHARS, 1012, 1025, (byte) 33 ); // Fill 13 of value (byte) 33 + Arrays.fill(CHARS, 1025, 1037, (byte) -19 ); // Fill 12 of value (byte) -19 + CHARS[1037] = 33; + Arrays.fill(CHARS, 1038, 1104, (byte) -19 ); // Fill 66 of value (byte) -19 + CHARS[1104] = 33; + Arrays.fill(CHARS, 1105, 1117, (byte) -19 ); // Fill 12 of value (byte) -19 + CHARS[1117] = 33; + Arrays.fill(CHARS, 1118, 1154, (byte) -19 ); // Fill 36 of value (byte) -19 + CHARS[1154] = 33; + Arrays.fill(CHARS, 1155, 1159, (byte) -87 ); // Fill 4 of value (byte) -87 + Arrays.fill(CHARS, 1159, 1168, (byte) 33 ); // Fill 9 of value (byte) 33 + Arrays.fill(CHARS, 1168, 1221, (byte) -19 ); // Fill 53 of value (byte) -19 + Arrays.fill(CHARS, 1221, 1223, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 1223, 1225, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 1225, 1227, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 1227, 1229, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 1229, 1232, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 1232, 1260, (byte) -19 ); // Fill 28 of value (byte) -19 + Arrays.fill(CHARS, 1260, 1262, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 1262, 1270, (byte) -19 ); // Fill 8 of value (byte) -19 + Arrays.fill(CHARS, 1270, 1272, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 1272, 1274, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 1274, 1329, (byte) 33 ); // Fill 55 of value (byte) 33 + Arrays.fill(CHARS, 1329, 1367, (byte) -19 ); // Fill 38 of value (byte) -19 + Arrays.fill(CHARS, 1367, 1369, (byte) 33 ); // Fill 2 of value (byte) 33 + CHARS[1369] = -19; + Arrays.fill(CHARS, 1370, 1377, (byte) 33 ); // Fill 7 of value (byte) 33 + Arrays.fill(CHARS, 1377, 1415, (byte) -19 ); // Fill 38 of value (byte) -19 + Arrays.fill(CHARS, 1415, 1425, (byte) 33 ); // Fill 10 of value (byte) 33 + Arrays.fill(CHARS, 1425, 1442, (byte) -87 ); // Fill 17 of value (byte) -87 + CHARS[1442] = 33; + Arrays.fill(CHARS, 1443, 1466, (byte) -87 ); // Fill 23 of value (byte) -87 + CHARS[1466] = 33; + Arrays.fill(CHARS, 1467, 1470, (byte) -87 ); // Fill 3 of value (byte) -87 + CHARS[1470] = 33; + CHARS[1471] = -87; + CHARS[1472] = 33; + Arrays.fill(CHARS, 1473, 1475, (byte) -87 ); // Fill 2 of value (byte) -87 + CHARS[1475] = 33; + CHARS[1476] = -87; + Arrays.fill(CHARS, 1477, 1488, (byte) 33 ); // Fill 11 of value (byte) 33 + Arrays.fill(CHARS, 1488, 1515, (byte) -19 ); // Fill 27 of value (byte) -19 + Arrays.fill(CHARS, 1515, 1520, (byte) 33 ); // Fill 5 of value (byte) 33 + Arrays.fill(CHARS, 1520, 1523, (byte) -19 ); // Fill 3 of value (byte) -19 + Arrays.fill(CHARS, 1523, 1569, (byte) 33 ); // Fill 46 of value (byte) 33 + Arrays.fill(CHARS, 1569, 1595, (byte) -19 ); // Fill 26 of value (byte) -19 + Arrays.fill(CHARS, 1595, 1600, (byte) 33 ); // Fill 5 of value (byte) 33 + CHARS[1600] = -87; + Arrays.fill(CHARS, 1601, 1611, (byte) -19 ); // Fill 10 of value (byte) -19 + Arrays.fill(CHARS, 1611, 1619, (byte) -87 ); // Fill 8 of value (byte) -87 + Arrays.fill(CHARS, 1619, 1632, (byte) 33 ); // Fill 13 of value (byte) 33 + Arrays.fill(CHARS, 1632, 1642, (byte) -87 ); // Fill 10 of value (byte) -87 + Arrays.fill(CHARS, 1642, 1648, (byte) 33 ); // Fill 6 of value (byte) 33 + CHARS[1648] = -87; + Arrays.fill(CHARS, 1649, 1720, (byte) -19 ); // Fill 71 of value (byte) -19 + Arrays.fill(CHARS, 1720, 1722, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 1722, 1727, (byte) -19 ); // Fill 5 of value (byte) -19 + CHARS[1727] = 33; + Arrays.fill(CHARS, 1728, 1743, (byte) -19 ); // Fill 15 of value (byte) -19 + CHARS[1743] = 33; + Arrays.fill(CHARS, 1744, 1748, (byte) -19 ); // Fill 4 of value (byte) -19 + CHARS[1748] = 33; + CHARS[1749] = -19; + Arrays.fill(CHARS, 1750, 1765, (byte) -87 ); // Fill 15 of value (byte) -87 + Arrays.fill(CHARS, 1765, 1767, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 1767, 1769, (byte) -87 ); // Fill 2 of value (byte) -87 + CHARS[1769] = 33; + Arrays.fill(CHARS, 1770, 1774, (byte) -87 ); // Fill 4 of value (byte) -87 + Arrays.fill(CHARS, 1774, 1776, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 1776, 1786, (byte) -87 ); // Fill 10 of value (byte) -87 + Arrays.fill(CHARS, 1786, 2305, (byte) 33 ); // Fill 519 of value (byte) 33 + Arrays.fill(CHARS, 2305, 2308, (byte) -87 ); // Fill 3 of value (byte) -87 + CHARS[2308] = 33; + Arrays.fill(CHARS, 2309, 2362, (byte) -19 ); // Fill 53 of value (byte) -19 + Arrays.fill(CHARS, 2362, 2364, (byte) 33 ); // Fill 2 of value (byte) 33 + CHARS[2364] = -87; + CHARS[2365] = -19; + Arrays.fill(CHARS, 2366, 2382, (byte) -87 ); // Fill 16 of value (byte) -87 + Arrays.fill(CHARS, 2382, 2385, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 2385, 2389, (byte) -87 ); // Fill 4 of value (byte) -87 + Arrays.fill(CHARS, 2389, 2392, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 2392, 2402, (byte) -19 ); // Fill 10 of value (byte) -19 + Arrays.fill(CHARS, 2402, 2404, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 2404, 2406, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 2406, 2416, (byte) -87 ); // Fill 10 of value (byte) -87 + Arrays.fill(CHARS, 2416, 2433, (byte) 33 ); // Fill 17 of value (byte) 33 + Arrays.fill(CHARS, 2433, 2436, (byte) -87 ); // Fill 3 of value (byte) -87 + CHARS[2436] = 33; + Arrays.fill(CHARS, 2437, 2445, (byte) -19 ); // Fill 8 of value (byte) -19 + Arrays.fill(CHARS, 2445, 2447, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 2447, 2449, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 2449, 2451, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 2451, 2473, (byte) -19 ); // Fill 22 of value (byte) -19 + CHARS[2473] = 33; + Arrays.fill(CHARS, 2474, 2481, (byte) -19 ); // Fill 7 of value (byte) -19 + CHARS[2481] = 33; + CHARS[2482] = -19; + Arrays.fill(CHARS, 2483, 2486, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 2486, 2490, (byte) -19 ); // Fill 4 of value (byte) -19 + Arrays.fill(CHARS, 2490, 2492, (byte) 33 ); // Fill 2 of value (byte) 33 + CHARS[2492] = -87; + CHARS[2493] = 33; + Arrays.fill(CHARS, 2494, 2501, (byte) -87 ); // Fill 7 of value (byte) -87 + Arrays.fill(CHARS, 2501, 2503, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 2503, 2505, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 2505, 2507, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 2507, 2510, (byte) -87 ); // Fill 3 of value (byte) -87 + Arrays.fill(CHARS, 2510, 2519, (byte) 33 ); // Fill 9 of value (byte) 33 + CHARS[2519] = -87; + Arrays.fill(CHARS, 2520, 2524, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 2524, 2526, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[2526] = 33; + Arrays.fill(CHARS, 2527, 2530, (byte) -19 ); // Fill 3 of value (byte) -19 + Arrays.fill(CHARS, 2530, 2532, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 2532, 2534, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 2534, 2544, (byte) -87 ); // Fill 10 of value (byte) -87 + Arrays.fill(CHARS, 2544, 2546, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 2546, 2562, (byte) 33 ); // Fill 16 of value (byte) 33 + CHARS[2562] = -87; + Arrays.fill(CHARS, 2563, 2565, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 2565, 2571, (byte) -19 ); // Fill 6 of value (byte) -19 + Arrays.fill(CHARS, 2571, 2575, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 2575, 2577, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 2577, 2579, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 2579, 2601, (byte) -19 ); // Fill 22 of value (byte) -19 + CHARS[2601] = 33; + Arrays.fill(CHARS, 2602, 2609, (byte) -19 ); // Fill 7 of value (byte) -19 + CHARS[2609] = 33; + Arrays.fill(CHARS, 2610, 2612, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[2612] = 33; + Arrays.fill(CHARS, 2613, 2615, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[2615] = 33; + Arrays.fill(CHARS, 2616, 2618, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 2618, 2620, (byte) 33 ); // Fill 2 of value (byte) 33 + CHARS[2620] = -87; + CHARS[2621] = 33; + Arrays.fill(CHARS, 2622, 2627, (byte) -87 ); // Fill 5 of value (byte) -87 + Arrays.fill(CHARS, 2627, 2631, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 2631, 2633, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 2633, 2635, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 2635, 2638, (byte) -87 ); // Fill 3 of value (byte) -87 + Arrays.fill(CHARS, 2638, 2649, (byte) 33 ); // Fill 11 of value (byte) 33 + Arrays.fill(CHARS, 2649, 2653, (byte) -19 ); // Fill 4 of value (byte) -19 + CHARS[2653] = 33; + CHARS[2654] = -19; + Arrays.fill(CHARS, 2655, 2662, (byte) 33 ); // Fill 7 of value (byte) 33 + Arrays.fill(CHARS, 2662, 2674, (byte) -87 ); // Fill 12 of value (byte) -87 + Arrays.fill(CHARS, 2674, 2677, (byte) -19 ); // Fill 3 of value (byte) -19 + Arrays.fill(CHARS, 2677, 2689, (byte) 33 ); // Fill 12 of value (byte) 33 + Arrays.fill(CHARS, 2689, 2692, (byte) -87 ); // Fill 3 of value (byte) -87 + CHARS[2692] = 33; + Arrays.fill(CHARS, 2693, 2700, (byte) -19 ); // Fill 7 of value (byte) -19 + CHARS[2700] = 33; + CHARS[2701] = -19; + CHARS[2702] = 33; + Arrays.fill(CHARS, 2703, 2706, (byte) -19 ); // Fill 3 of value (byte) -19 + CHARS[2706] = 33; + Arrays.fill(CHARS, 2707, 2729, (byte) -19 ); // Fill 22 of value (byte) -19 + CHARS[2729] = 33; + Arrays.fill(CHARS, 2730, 2737, (byte) -19 ); // Fill 7 of value (byte) -19 + CHARS[2737] = 33; + Arrays.fill(CHARS, 2738, 2740, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[2740] = 33; + Arrays.fill(CHARS, 2741, 2746, (byte) -19 ); // Fill 5 of value (byte) -19 + Arrays.fill(CHARS, 2746, 2748, (byte) 33 ); // Fill 2 of value (byte) 33 + CHARS[2748] = -87; + CHARS[2749] = -19; + Arrays.fill(CHARS, 2750, 2758, (byte) -87 ); // Fill 8 of value (byte) -87 + CHARS[2758] = 33; + Arrays.fill(CHARS, 2759, 2762, (byte) -87 ); // Fill 3 of value (byte) -87 + CHARS[2762] = 33; + Arrays.fill(CHARS, 2763, 2766, (byte) -87 ); // Fill 3 of value (byte) -87 + Arrays.fill(CHARS, 2766, 2784, (byte) 33 ); // Fill 18 of value (byte) 33 + CHARS[2784] = -19; + Arrays.fill(CHARS, 2785, 2790, (byte) 33 ); // Fill 5 of value (byte) 33 + Arrays.fill(CHARS, 2790, 2800, (byte) -87 ); // Fill 10 of value (byte) -87 + Arrays.fill(CHARS, 2800, 2817, (byte) 33 ); // Fill 17 of value (byte) 33 + Arrays.fill(CHARS, 2817, 2820, (byte) -87 ); // Fill 3 of value (byte) -87 + CHARS[2820] = 33; + Arrays.fill(CHARS, 2821, 2829, (byte) -19 ); // Fill 8 of value (byte) -19 + Arrays.fill(CHARS, 2829, 2831, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 2831, 2833, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 2833, 2835, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 2835, 2857, (byte) -19 ); // Fill 22 of value (byte) -19 + CHARS[2857] = 33; + Arrays.fill(CHARS, 2858, 2865, (byte) -19 ); // Fill 7 of value (byte) -19 + CHARS[2865] = 33; + Arrays.fill(CHARS, 2866, 2868, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 2868, 2870, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 2870, 2874, (byte) -19 ); // Fill 4 of value (byte) -19 + Arrays.fill(CHARS, 2874, 2876, (byte) 33 ); // Fill 2 of value (byte) 33 + CHARS[2876] = -87; + CHARS[2877] = -19; + Arrays.fill(CHARS, 2878, 2884, (byte) -87 ); // Fill 6 of value (byte) -87 + Arrays.fill(CHARS, 2884, 2887, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 2887, 2889, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 2889, 2891, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 2891, 2894, (byte) -87 ); // Fill 3 of value (byte) -87 + Arrays.fill(CHARS, 2894, 2902, (byte) 33 ); // Fill 8 of value (byte) 33 + Arrays.fill(CHARS, 2902, 2904, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 2904, 2908, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 2908, 2910, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[2910] = 33; + Arrays.fill(CHARS, 2911, 2914, (byte) -19 ); // Fill 3 of value (byte) -19 + Arrays.fill(CHARS, 2914, 2918, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 2918, 2928, (byte) -87 ); // Fill 10 of value (byte) -87 + Arrays.fill(CHARS, 2928, 2946, (byte) 33 ); // Fill 18 of value (byte) 33 + Arrays.fill(CHARS, 2946, 2948, (byte) -87 ); // Fill 2 of value (byte) -87 + CHARS[2948] = 33; + Arrays.fill(CHARS, 2949, 2955, (byte) -19 ); // Fill 6 of value (byte) -19 + Arrays.fill(CHARS, 2955, 2958, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 2958, 2961, (byte) -19 ); // Fill 3 of value (byte) -19 + CHARS[2961] = 33; + Arrays.fill(CHARS, 2962, 2966, (byte) -19 ); // Fill 4 of value (byte) -19 + Arrays.fill(CHARS, 2966, 2969, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 2969, 2971, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[2971] = 33; + CHARS[2972] = -19; + CHARS[2973] = 33; + Arrays.fill(CHARS, 2974, 2976, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 2976, 2979, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 2979, 2981, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 2981, 2984, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 2984, 2987, (byte) -19 ); // Fill 3 of value (byte) -19 + Arrays.fill(CHARS, 2987, 2990, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 2990, 2998, (byte) -19 ); // Fill 8 of value (byte) -19 + CHARS[2998] = 33; + Arrays.fill(CHARS, 2999, 3002, (byte) -19 ); // Fill 3 of value (byte) -19 + Arrays.fill(CHARS, 3002, 3006, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 3006, 3011, (byte) -87 ); // Fill 5 of value (byte) -87 + Arrays.fill(CHARS, 3011, 3014, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 3014, 3017, (byte) -87 ); // Fill 3 of value (byte) -87 + CHARS[3017] = 33; + Arrays.fill(CHARS, 3018, 3022, (byte) -87 ); // Fill 4 of value (byte) -87 + Arrays.fill(CHARS, 3022, 3031, (byte) 33 ); // Fill 9 of value (byte) 33 + CHARS[3031] = -87; + Arrays.fill(CHARS, 3032, 3047, (byte) 33 ); // Fill 15 of value (byte) 33 + Arrays.fill(CHARS, 3047, 3056, (byte) -87 ); // Fill 9 of value (byte) -87 + Arrays.fill(CHARS, 3056, 3073, (byte) 33 ); // Fill 17 of value (byte) 33 + Arrays.fill(CHARS, 3073, 3076, (byte) -87 ); // Fill 3 of value (byte) -87 + CHARS[3076] = 33; + Arrays.fill(CHARS, 3077, 3085, (byte) -19 ); // Fill 8 of value (byte) -19 + CHARS[3085] = 33; + Arrays.fill(CHARS, 3086, 3089, (byte) -19 ); // Fill 3 of value (byte) -19 + CHARS[3089] = 33; + Arrays.fill(CHARS, 3090, 3113, (byte) -19 ); // Fill 23 of value (byte) -19 + CHARS[3113] = 33; + Arrays.fill(CHARS, 3114, 3124, (byte) -19 ); // Fill 10 of value (byte) -19 + CHARS[3124] = 33; + Arrays.fill(CHARS, 3125, 3130, (byte) -19 ); // Fill 5 of value (byte) -19 + Arrays.fill(CHARS, 3130, 3134, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 3134, 3141, (byte) -87 ); // Fill 7 of value (byte) -87 + CHARS[3141] = 33; + Arrays.fill(CHARS, 3142, 3145, (byte) -87 ); // Fill 3 of value (byte) -87 + CHARS[3145] = 33; + Arrays.fill(CHARS, 3146, 3150, (byte) -87 ); // Fill 4 of value (byte) -87 + Arrays.fill(CHARS, 3150, 3157, (byte) 33 ); // Fill 7 of value (byte) 33 + Arrays.fill(CHARS, 3157, 3159, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 3159, 3168, (byte) 33 ); // Fill 9 of value (byte) 33 + Arrays.fill(CHARS, 3168, 3170, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 3170, 3174, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 3174, 3184, (byte) -87 ); // Fill 10 of value (byte) -87 + Arrays.fill(CHARS, 3184, 3202, (byte) 33 ); // Fill 18 of value (byte) 33 + Arrays.fill(CHARS, 3202, 3204, (byte) -87 ); // Fill 2 of value (byte) -87 + CHARS[3204] = 33; + Arrays.fill(CHARS, 3205, 3213, (byte) -19 ); // Fill 8 of value (byte) -19 + CHARS[3213] = 33; + Arrays.fill(CHARS, 3214, 3217, (byte) -19 ); // Fill 3 of value (byte) -19 + CHARS[3217] = 33; + Arrays.fill(CHARS, 3218, 3241, (byte) -19 ); // Fill 23 of value (byte) -19 + CHARS[3241] = 33; + Arrays.fill(CHARS, 3242, 3252, (byte) -19 ); // Fill 10 of value (byte) -19 + CHARS[3252] = 33; + Arrays.fill(CHARS, 3253, 3258, (byte) -19 ); // Fill 5 of value (byte) -19 + Arrays.fill(CHARS, 3258, 3262, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 3262, 3269, (byte) -87 ); // Fill 7 of value (byte) -87 + CHARS[3269] = 33; + Arrays.fill(CHARS, 3270, 3273, (byte) -87 ); // Fill 3 of value (byte) -87 + CHARS[3273] = 33; + Arrays.fill(CHARS, 3274, 3278, (byte) -87 ); // Fill 4 of value (byte) -87 + Arrays.fill(CHARS, 3278, 3285, (byte) 33 ); // Fill 7 of value (byte) 33 + Arrays.fill(CHARS, 3285, 3287, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 3287, 3294, (byte) 33 ); // Fill 7 of value (byte) 33 + CHARS[3294] = -19; + CHARS[3295] = 33; + Arrays.fill(CHARS, 3296, 3298, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 3298, 3302, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 3302, 3312, (byte) -87 ); // Fill 10 of value (byte) -87 + Arrays.fill(CHARS, 3312, 3330, (byte) 33 ); // Fill 18 of value (byte) 33 + Arrays.fill(CHARS, 3330, 3332, (byte) -87 ); // Fill 2 of value (byte) -87 + CHARS[3332] = 33; + Arrays.fill(CHARS, 3333, 3341, (byte) -19 ); // Fill 8 of value (byte) -19 + CHARS[3341] = 33; + Arrays.fill(CHARS, 3342, 3345, (byte) -19 ); // Fill 3 of value (byte) -19 + CHARS[3345] = 33; + Arrays.fill(CHARS, 3346, 3369, (byte) -19 ); // Fill 23 of value (byte) -19 + CHARS[3369] = 33; + Arrays.fill(CHARS, 3370, 3386, (byte) -19 ); // Fill 16 of value (byte) -19 + Arrays.fill(CHARS, 3386, 3390, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 3390, 3396, (byte) -87 ); // Fill 6 of value (byte) -87 + Arrays.fill(CHARS, 3396, 3398, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 3398, 3401, (byte) -87 ); // Fill 3 of value (byte) -87 + CHARS[3401] = 33; + Arrays.fill(CHARS, 3402, 3406, (byte) -87 ); // Fill 4 of value (byte) -87 + Arrays.fill(CHARS, 3406, 3415, (byte) 33 ); // Fill 9 of value (byte) 33 + CHARS[3415] = -87; + Arrays.fill(CHARS, 3416, 3424, (byte) 33 ); // Fill 8 of value (byte) 33 + Arrays.fill(CHARS, 3424, 3426, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 3426, 3430, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 3430, 3440, (byte) -87 ); // Fill 10 of value (byte) -87 + Arrays.fill(CHARS, 3440, 3585, (byte) 33 ); // Fill 145 of value (byte) 33 + Arrays.fill(CHARS, 3585, 3631, (byte) -19 ); // Fill 46 of value (byte) -19 + CHARS[3631] = 33; + CHARS[3632] = -19; + CHARS[3633] = -87; + Arrays.fill(CHARS, 3634, 3636, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 3636, 3643, (byte) -87 ); // Fill 7 of value (byte) -87 + Arrays.fill(CHARS, 3643, 3648, (byte) 33 ); // Fill 5 of value (byte) 33 + Arrays.fill(CHARS, 3648, 3654, (byte) -19 ); // Fill 6 of value (byte) -19 + Arrays.fill(CHARS, 3654, 3663, (byte) -87 ); // Fill 9 of value (byte) -87 + CHARS[3663] = 33; + Arrays.fill(CHARS, 3664, 3674, (byte) -87 ); // Fill 10 of value (byte) -87 + Arrays.fill(CHARS, 3674, 3713, (byte) 33 ); // Fill 39 of value (byte) 33 + Arrays.fill(CHARS, 3713, 3715, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[3715] = 33; + CHARS[3716] = -19; + Arrays.fill(CHARS, 3717, 3719, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 3719, 3721, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[3721] = 33; + CHARS[3722] = -19; + Arrays.fill(CHARS, 3723, 3725, (byte) 33 ); // Fill 2 of value (byte) 33 + CHARS[3725] = -19; + Arrays.fill(CHARS, 3726, 3732, (byte) 33 ); // Fill 6 of value (byte) 33 + Arrays.fill(CHARS, 3732, 3736, (byte) -19 ); // Fill 4 of value (byte) -19 + CHARS[3736] = 33; + Arrays.fill(CHARS, 3737, 3744, (byte) -19 ); // Fill 7 of value (byte) -19 + CHARS[3744] = 33; + Arrays.fill(CHARS, 3745, 3748, (byte) -19 ); // Fill 3 of value (byte) -19 + CHARS[3748] = 33; + CHARS[3749] = -19; + CHARS[3750] = 33; + CHARS[3751] = -19; + Arrays.fill(CHARS, 3752, 3754, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 3754, 3756, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[3756] = 33; + Arrays.fill(CHARS, 3757, 3759, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[3759] = 33; + CHARS[3760] = -19; + CHARS[3761] = -87; + Arrays.fill(CHARS, 3762, 3764, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 3764, 3770, (byte) -87 ); // Fill 6 of value (byte) -87 + CHARS[3770] = 33; + Arrays.fill(CHARS, 3771, 3773, (byte) -87 ); // Fill 2 of value (byte) -87 + CHARS[3773] = -19; + Arrays.fill(CHARS, 3774, 3776, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 3776, 3781, (byte) -19 ); // Fill 5 of value (byte) -19 + CHARS[3781] = 33; + CHARS[3782] = -87; + CHARS[3783] = 33; + Arrays.fill(CHARS, 3784, 3790, (byte) -87 ); // Fill 6 of value (byte) -87 + Arrays.fill(CHARS, 3790, 3792, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 3792, 3802, (byte) -87 ); // Fill 10 of value (byte) -87 + Arrays.fill(CHARS, 3802, 3864, (byte) 33 ); // Fill 62 of value (byte) 33 + Arrays.fill(CHARS, 3864, 3866, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 3866, 3872, (byte) 33 ); // Fill 6 of value (byte) 33 + Arrays.fill(CHARS, 3872, 3882, (byte) -87 ); // Fill 10 of value (byte) -87 + Arrays.fill(CHARS, 3882, 3893, (byte) 33 ); // Fill 11 of value (byte) 33 + CHARS[3893] = -87; + CHARS[3894] = 33; + CHARS[3895] = -87; + CHARS[3896] = 33; + CHARS[3897] = -87; + Arrays.fill(CHARS, 3898, 3902, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 3902, 3904, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 3904, 3912, (byte) -19 ); // Fill 8 of value (byte) -19 + CHARS[3912] = 33; + Arrays.fill(CHARS, 3913, 3946, (byte) -19 ); // Fill 33 of value (byte) -19 + Arrays.fill(CHARS, 3946, 3953, (byte) 33 ); // Fill 7 of value (byte) 33 + Arrays.fill(CHARS, 3953, 3973, (byte) -87 ); // Fill 20 of value (byte) -87 + CHARS[3973] = 33; + Arrays.fill(CHARS, 3974, 3980, (byte) -87 ); // Fill 6 of value (byte) -87 + Arrays.fill(CHARS, 3980, 3984, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 3984, 3990, (byte) -87 ); // Fill 6 of value (byte) -87 + CHARS[3990] = 33; + CHARS[3991] = -87; + CHARS[3992] = 33; + Arrays.fill(CHARS, 3993, 4014, (byte) -87 ); // Fill 21 of value (byte) -87 + Arrays.fill(CHARS, 4014, 4017, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 4017, 4024, (byte) -87 ); // Fill 7 of value (byte) -87 + CHARS[4024] = 33; + CHARS[4025] = -87; + Arrays.fill(CHARS, 4026, 4256, (byte) 33 ); // Fill 230 of value (byte) 33 + Arrays.fill(CHARS, 4256, 4294, (byte) -19 ); // Fill 38 of value (byte) -19 + Arrays.fill(CHARS, 4294, 4304, (byte) 33 ); // Fill 10 of value (byte) 33 + Arrays.fill(CHARS, 4304, 4343, (byte) -19 ); // Fill 39 of value (byte) -19 + Arrays.fill(CHARS, 4343, 4352, (byte) 33 ); // Fill 9 of value (byte) 33 + CHARS[4352] = -19; + CHARS[4353] = 33; + Arrays.fill(CHARS, 4354, 4356, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[4356] = 33; + Arrays.fill(CHARS, 4357, 4360, (byte) -19 ); // Fill 3 of value (byte) -19 + CHARS[4360] = 33; + CHARS[4361] = -19; + CHARS[4362] = 33; + Arrays.fill(CHARS, 4363, 4365, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[4365] = 33; + Arrays.fill(CHARS, 4366, 4371, (byte) -19 ); // Fill 5 of value (byte) -19 + Arrays.fill(CHARS, 4371, 4412, (byte) 33 ); // Fill 41 of value (byte) 33 + CHARS[4412] = -19; + CHARS[4413] = 33; + CHARS[4414] = -19; + CHARS[4415] = 33; + CHARS[4416] = -19; + Arrays.fill(CHARS, 4417, 4428, (byte) 33 ); // Fill 11 of value (byte) 33 + CHARS[4428] = -19; + CHARS[4429] = 33; + CHARS[4430] = -19; + CHARS[4431] = 33; + CHARS[4432] = -19; + Arrays.fill(CHARS, 4433, 4436, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 4436, 4438, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 4438, 4441, (byte) 33 ); // Fill 3 of value (byte) 33 + CHARS[4441] = -19; + Arrays.fill(CHARS, 4442, 4447, (byte) 33 ); // Fill 5 of value (byte) 33 + Arrays.fill(CHARS, 4447, 4450, (byte) -19 ); // Fill 3 of value (byte) -19 + CHARS[4450] = 33; + CHARS[4451] = -19; + CHARS[4452] = 33; + CHARS[4453] = -19; + CHARS[4454] = 33; + CHARS[4455] = -19; + CHARS[4456] = 33; + CHARS[4457] = -19; + Arrays.fill(CHARS, 4458, 4461, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 4461, 4463, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 4463, 4466, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 4466, 4468, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[4468] = 33; + CHARS[4469] = -19; + Arrays.fill(CHARS, 4470, 4510, (byte) 33 ); // Fill 40 of value (byte) 33 + CHARS[4510] = -19; + Arrays.fill(CHARS, 4511, 4520, (byte) 33 ); // Fill 9 of value (byte) 33 + CHARS[4520] = -19; + Arrays.fill(CHARS, 4521, 4523, (byte) 33 ); // Fill 2 of value (byte) 33 + CHARS[4523] = -19; + Arrays.fill(CHARS, 4524, 4526, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 4526, 4528, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 4528, 4535, (byte) 33 ); // Fill 7 of value (byte) 33 + Arrays.fill(CHARS, 4535, 4537, (byte) -19 ); // Fill 2 of value (byte) -19 + CHARS[4537] = 33; + CHARS[4538] = -19; + CHARS[4539] = 33; + Arrays.fill(CHARS, 4540, 4547, (byte) -19 ); // Fill 7 of value (byte) -19 + Arrays.fill(CHARS, 4547, 4587, (byte) 33 ); // Fill 40 of value (byte) 33 + CHARS[4587] = -19; + Arrays.fill(CHARS, 4588, 4592, (byte) 33 ); // Fill 4 of value (byte) 33 + CHARS[4592] = -19; + Arrays.fill(CHARS, 4593, 4601, (byte) 33 ); // Fill 8 of value (byte) 33 + CHARS[4601] = -19; + Arrays.fill(CHARS, 4602, 7680, (byte) 33 ); // Fill 3078 of value (byte) 33 + Arrays.fill(CHARS, 7680, 7836, (byte) -19 ); // Fill 156 of value (byte) -19 + Arrays.fill(CHARS, 7836, 7840, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 7840, 7930, (byte) -19 ); // Fill 90 of value (byte) -19 + Arrays.fill(CHARS, 7930, 7936, (byte) 33 ); // Fill 6 of value (byte) 33 + Arrays.fill(CHARS, 7936, 7958, (byte) -19 ); // Fill 22 of value (byte) -19 + Arrays.fill(CHARS, 7958, 7960, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 7960, 7966, (byte) -19 ); // Fill 6 of value (byte) -19 + Arrays.fill(CHARS, 7966, 7968, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 7968, 8006, (byte) -19 ); // Fill 38 of value (byte) -19 + Arrays.fill(CHARS, 8006, 8008, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 8008, 8014, (byte) -19 ); // Fill 6 of value (byte) -19 + Arrays.fill(CHARS, 8014, 8016, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 8016, 8024, (byte) -19 ); // Fill 8 of value (byte) -19 + CHARS[8024] = 33; + CHARS[8025] = -19; + CHARS[8026] = 33; + CHARS[8027] = -19; + CHARS[8028] = 33; + CHARS[8029] = -19; + CHARS[8030] = 33; + Arrays.fill(CHARS, 8031, 8062, (byte) -19 ); // Fill 31 of value (byte) -19 + Arrays.fill(CHARS, 8062, 8064, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 8064, 8117, (byte) -19 ); // Fill 53 of value (byte) -19 + CHARS[8117] = 33; + Arrays.fill(CHARS, 8118, 8125, (byte) -19 ); // Fill 7 of value (byte) -19 + CHARS[8125] = 33; + CHARS[8126] = -19; + Arrays.fill(CHARS, 8127, 8130, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 8130, 8133, (byte) -19 ); // Fill 3 of value (byte) -19 + CHARS[8133] = 33; + Arrays.fill(CHARS, 8134, 8141, (byte) -19 ); // Fill 7 of value (byte) -19 + Arrays.fill(CHARS, 8141, 8144, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 8144, 8148, (byte) -19 ); // Fill 4 of value (byte) -19 + Arrays.fill(CHARS, 8148, 8150, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 8150, 8156, (byte) -19 ); // Fill 6 of value (byte) -19 + Arrays.fill(CHARS, 8156, 8160, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 8160, 8173, (byte) -19 ); // Fill 13 of value (byte) -19 + Arrays.fill(CHARS, 8173, 8178, (byte) 33 ); // Fill 5 of value (byte) 33 + Arrays.fill(CHARS, 8178, 8181, (byte) -19 ); // Fill 3 of value (byte) -19 + CHARS[8181] = 33; + Arrays.fill(CHARS, 8182, 8189, (byte) -19 ); // Fill 7 of value (byte) -19 + Arrays.fill(CHARS, 8189, 8400, (byte) 33 ); // Fill 211 of value (byte) 33 + Arrays.fill(CHARS, 8400, 8413, (byte) -87 ); // Fill 13 of value (byte) -87 + Arrays.fill(CHARS, 8413, 8417, (byte) 33 ); // Fill 4 of value (byte) 33 + CHARS[8417] = -87; + Arrays.fill(CHARS, 8418, 8486, (byte) 33 ); // Fill 68 of value (byte) 33 + CHARS[8486] = -19; + Arrays.fill(CHARS, 8487, 8490, (byte) 33 ); // Fill 3 of value (byte) 33 + Arrays.fill(CHARS, 8490, 8492, (byte) -19 ); // Fill 2 of value (byte) -19 + Arrays.fill(CHARS, 8492, 8494, (byte) 33 ); // Fill 2 of value (byte) 33 + CHARS[8494] = -19; + Arrays.fill(CHARS, 8495, 8576, (byte) 33 ); // Fill 81 of value (byte) 33 + Arrays.fill(CHARS, 8576, 8579, (byte) -19 ); // Fill 3 of value (byte) -19 + Arrays.fill(CHARS, 8579, 12293, (byte) 33 ); // Fill 3714 of value (byte) 33 + CHARS[12293] = -87; + CHARS[12294] = 33; + CHARS[12295] = -19; + Arrays.fill(CHARS, 12296, 12321, (byte) 33 ); // Fill 25 of value (byte) 33 + Arrays.fill(CHARS, 12321, 12330, (byte) -19 ); // Fill 9 of value (byte) -19 + Arrays.fill(CHARS, 12330, 12336, (byte) -87 ); // Fill 6 of value (byte) -87 + CHARS[12336] = 33; + Arrays.fill(CHARS, 12337, 12342, (byte) -87 ); // Fill 5 of value (byte) -87 + Arrays.fill(CHARS, 12342, 12353, (byte) 33 ); // Fill 11 of value (byte) 33 + Arrays.fill(CHARS, 12353, 12437, (byte) -19 ); // Fill 84 of value (byte) -19 + Arrays.fill(CHARS, 12437, 12441, (byte) 33 ); // Fill 4 of value (byte) 33 + Arrays.fill(CHARS, 12441, 12443, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 12443, 12445, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 12445, 12447, (byte) -87 ); // Fill 2 of value (byte) -87 + Arrays.fill(CHARS, 12447, 12449, (byte) 33 ); // Fill 2 of value (byte) 33 + Arrays.fill(CHARS, 12449, 12539, (byte) -19 ); // Fill 90 of value (byte) -19 + CHARS[12539] = 33; + Arrays.fill(CHARS, 12540, 12543, (byte) -87 ); // Fill 3 of value (byte) -87 + Arrays.fill(CHARS, 12543, 12549, (byte) 33 ); // Fill 6 of value (byte) 33 + Arrays.fill(CHARS, 12549, 12589, (byte) -19 ); // Fill 40 of value (byte) -19 + Arrays.fill(CHARS, 12589, 19968, (byte) 33 ); // Fill 7379 of value (byte) 33 + Arrays.fill(CHARS, 19968, 40870, (byte) -19 ); // Fill 20902 of value (byte) -19 + Arrays.fill(CHARS, 40870, 44032, (byte) 33 ); // Fill 3162 of value (byte) 33 + Arrays.fill(CHARS, 44032, 55204, (byte) -19 ); // Fill 11172 of value (byte) -19 + Arrays.fill(CHARS, 55204, 55296, (byte) 33 ); // Fill 92 of value (byte) 33 + Arrays.fill(CHARS, 57344, 65534, (byte) 33 ); // Fill 8190 of value (byte) 33 + + } // () + + // + // Public static methods + // + + /** + * Returns true if the specified character is a supplemental character. + * + * @param c The character to check. + */ + public static boolean isSupplemental(int c) { + return (c >= 0x10000 && c <= 0x10FFFF); + } + + /** + * Returns true the supplemental character corresponding to the given + * surrogates. + * + * @param h The high surrogate. + * @param l The low surrogate. + */ + public static int supplemental(char h, char l) { + return (h - 0xD800) * 0x400 + (l - 0xDC00) + 0x10000; + } + + /** + * Returns the high surrogate of a supplemental character + * + * @param c The supplemental character to "split". + */ + public static char highSurrogate(int c) { + return (char) (((c - 0x00010000) >> 10) + 0xD800); + } + + /** + * Returns the low surrogate of a supplemental character + * + * @param c The supplemental character to "split". + */ + public static char lowSurrogate(int c) { + return (char) (((c - 0x00010000) & 0x3FF) + 0xDC00); + } + + /** + * Returns whether the given character is a high surrogate + * + * @param c The character to check. + */ + public static boolean isHighSurrogate(int c) { + return (0xD800 <= c && c <= 0xDBFF); + } + + /** + * Returns whether the given character is a low surrogate + * + * @param c The character to check. + */ + public static boolean isLowSurrogate(int c) { + return (0xDC00 <= c && c <= 0xDFFF); + } + + + /** + * Returns true if the specified character is valid. This method + * also checks the surrogate character range from 0x10000 to 0x10FFFF. + *

+ * If the program chooses to apply the mask directly to the + * CHARS array, then they are responsible for checking + * the surrogate character range. + * + * @param c The character to check. + */ + public static boolean isValid(int c) { + return (c < 0x10000 && (CHARS[c] & MASK_VALID) != 0) || + (0x10000 <= c && c <= 0x10FFFF); + } // isValid(int):boolean + + /** + * Returns true if the specified character is invalid. + * + * @param c The character to check. + */ + public static boolean isInvalid(int c) { + return !isValid(c); + } // isInvalid(int):boolean + + /** + * Returns true if the specified character can be considered content. + * + * @param c The character to check. + */ + public static boolean isContent(int c) { + return (c < 0x10000 && (CHARS[c] & MASK_CONTENT) != 0) || + (0x10000 <= c && c <= 0x10FFFF); + } // isContent(int):boolean + + /** + * Returns true if the specified character can be considered markup. + * Markup characters include '<', '&', and '%'. + * + * @param c The character to check. + */ + public static boolean isMarkup(int c) { + return c == '<' || c == '&' || c == '%'; + } // isMarkup(int):boolean + + /** + * Returns true if the specified character is a space character + * as defined by production [3] in the XML 1.0 specification. + * + * @param c The character to check. + */ + public static boolean isSpace(int c) { + return c <= 0x20 && (CHARS[c] & MASK_SPACE) != 0; + } // isSpace(int):boolean + + /** + * Returns true if the specified character is a valid name start + * character as defined by production [5] in the XML 1.0 + * specification. + * + * @param c The character to check. + */ + public static boolean isNameStart(int c) { + return c < 0x10000 && (CHARS[c] & MASK_NAME_START) != 0; + } // isNameStart(int):boolean + + /** + * Returns true if the specified character is a valid name + * character as defined by production [4] in the XML 1.0 + * specification. + * + * @param c The character to check. + */ + public static boolean isName(int c) { + return c < 0x10000 && (CHARS[c] & MASK_NAME) != 0; + } // isName(int):boolean + + /** + * Returns true if the specified character is a valid NCName start + * character as defined by production [4] in Namespaces in XML + * recommendation. + * + * @param c The character to check. + */ + public static boolean isNCNameStart(int c) { + return c < 0x10000 && (CHARS[c] & MASK_NCNAME_START) != 0; + } // isNCNameStart(int):boolean + + /** + * Returns true if the specified character is a valid NCName + * character as defined by production [5] in Namespaces in XML + * recommendation. + * + * @param c The character to check. + */ + public static boolean isNCName(int c) { + return c < 0x10000 && (CHARS[c] & MASK_NCNAME) != 0; + } // isNCName(int):boolean + + /** + * Returns true if the specified character is a valid Pubid + * character as defined by production [13] in the XML 1.0 + * specification. + * + * @param c The character to check. + */ + public static boolean isPubid(int c) { + return c < 0x10000 && (CHARS[c] & MASK_PUBID) != 0; + } // isPubid(int):boolean + + /* + * [5] Name ::= (Letter | '_' | ':') (NameChar)* + */ + /** + * Check to see if a string is a valid Name according to [5] + * in the XML 1.0 Recommendation + * + * @param name string to check + * @return true if name is a valid Name + */ + public static boolean isValidName(String name) { + if (name.length() == 0) + return false; + char ch = name.charAt(0); + if( isNameStart(ch) == false) + return false; + for (int i = 1; i < name.length(); i++ ) { + ch = name.charAt(i); + if( isName( ch ) == false ){ + return false; + } + } + return true; + } // isValidName(String):boolean + + + /* + * from the namespace rec + * [4] NCName ::= (Letter | '_') (NCNameChar)* + */ + /** + * Check to see if a string is a valid NCName according to [4] + * from the XML Namespaces 1.0 Recommendation + * + * @param ncName string to check + * @return true if name is a valid NCName + */ + public static boolean isValidNCName(String ncName) { + if (ncName.length() == 0) + return false; + char ch = ncName.charAt(0); + if( isNCNameStart(ch) == false) + return false; + for (int i = 1; i < ncName.length(); i++ ) { + ch = ncName.charAt(i); + if( isNCName( ch ) == false ){ + return false; + } + } + return true; + } // isValidNCName(String):boolean + + /* + * [7] Nmtoken ::= (NameChar)+ + */ + /** + * Check to see if a string is a valid Nmtoken according to [7] + * in the XML 1.0 Recommendation + * + * @param nmtoken string to check + * @return true if nmtoken is a valid Nmtoken + */ + public static boolean isValidNmtoken(String nmtoken) { + if (nmtoken.length() == 0) + return false; + for (int i = 0; i < nmtoken.length(); i++ ) { + char ch = nmtoken.charAt(i); + if( ! isName( ch ) ){ + return false; + } + } + return true; + } // isValidName(String):boolean + + + + + + // encodings + + /** + * Returns true if the encoding name is a valid IANA encoding. + * This method does not verify that there is a decoder available + * for this encoding, only that the characters are valid for an + * IANA encoding name. + * + * @param ianaEncoding The IANA encoding name. + */ + public static boolean isValidIANAEncoding(String ianaEncoding) { + if (ianaEncoding != null) { + int length = ianaEncoding.length(); + if (length > 0) { + char c = ianaEncoding.charAt(0); + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) { + for (int i = 1; i < length; i++) { + c = ianaEncoding.charAt(i); + if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && + (c < '0' || c > '9') && c != '.' && c != '_' && + c != '-') { + return false; + } + } + return true; + } + } + } + return false; + } // isValidIANAEncoding(String):boolean + + /** + * Returns true if the encoding name is a valid Java encoding. + * This method does not verify that there is a decoder available + * for this encoding, only that the characters are valid for an + * Java encoding name. + * + * @param javaEncoding The Java encoding name. + */ + public static boolean isValidJavaEncoding(String javaEncoding) { + if (javaEncoding != null) { + int length = javaEncoding.length(); + if (length > 0) { + for (int i = 1; i < length; i++) { + char c = javaEncoding.charAt(i); + if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && + (c < '0' || c > '9') && c != '.' && c != '_' && + c != '-') { + return false; + } + } + return true; + } + } + return false; + } // isValidIANAEncoding(String):boolean + + +} // class XMLChar diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLEncodingDetector.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLEncodingDetector.java new file mode 100644 index 000000000..a5c8a9308 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLEncodingDetector.java @@ -0,0 +1,1637 @@ +/* + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 1999, International + * Business Machines, Inc., http://www.apache.org. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.jasper.xmlparser; + +import java.io.EOFException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.Reader; +import java.util.Locale; +import java.util.jar.JarFile; + +import org.apache.jasper.JasperException; +import org.apache.jasper.JspCompilationContext; +import org.apache.jasper.compiler.ErrorDispatcher; +import org.apache.jasper.compiler.JspUtil; + +public class XMLEncodingDetector { + + private InputStream stream; + private String encoding; + private boolean isEncodingSetInProlog; + private boolean isBomPresent; + private int skip; + private Boolean isBigEndian; + private Reader reader; + + // org.apache.xerces.impl.XMLEntityManager fields + public static final int DEFAULT_BUFFER_SIZE = 2048; + public static final int DEFAULT_XMLDECL_BUFFER_SIZE = 64; + private boolean fAllowJavaEncodings; + private SymbolTable fSymbolTable; + private XMLEncodingDetector fCurrentEntity; + private int fBufferSize = DEFAULT_BUFFER_SIZE; + + // org.apache.xerces.impl.XMLEntityManager.ScannedEntity fields + private int lineNumber = 1; + private int columnNumber = 1; + private boolean literal; + private char[] ch = new char[DEFAULT_BUFFER_SIZE]; + private int position; + private int count; + private boolean mayReadChunks = false; + + // org.apache.xerces.impl.XMLScanner fields + private XMLString fString = new XMLString(); + private XMLStringBuffer fStringBuffer = new XMLStringBuffer(); + private XMLStringBuffer fStringBuffer2 = new XMLStringBuffer(); + private final static String fVersionSymbol = "version"; + private final static String fEncodingSymbol = "encoding"; + private final static String fStandaloneSymbol = "standalone"; + + // org.apache.xerces.impl.XMLDocumentFragmentScannerImpl fields + private int fMarkupDepth = 0; + private String[] fStrings = new String[3]; + + private ErrorDispatcher err; + + /** + * Constructor + */ + public XMLEncodingDetector() { + fSymbolTable = new SymbolTable(); + fCurrentEntity = this; + } + + /** + * Autodetects the encoding of the XML document supplied by the given + * input stream. + * + * Encoding autodetection is done according to the XML 1.0 specification, + * Appendix F.1: Detection Without External Encoding Information. + * + * @return Two-element array, where the first element (of type + * java.lang.String) contains the name of the (auto)detected encoding, and + * the second element (of type java.lang.Boolean) specifies whether the + * encoding was specified using the 'encoding' attribute of an XML prolog + * (TRUE) or autodetected (FALSE). + */ + public static Object[] getEncoding(String fname, JarFile jarFile, + JspCompilationContext ctxt, + ErrorDispatcher err) + throws IOException, JasperException + { + InputStream inStream = JspUtil.getInputStream(fname, jarFile, ctxt, + err); + XMLEncodingDetector detector = new XMLEncodingDetector(); + Object[] ret = detector.getEncoding(inStream, err); + inStream.close(); + + return ret; + } + + private Object[] getEncoding(InputStream in, ErrorDispatcher err) + throws IOException, JasperException + { + this.stream = in; + this.err=err; + createInitialReader(); + scanXMLDecl(); + + return new Object[] { this.encoding, + Boolean.valueOf(this.isEncodingSetInProlog), + Boolean.valueOf(this.isBomPresent), + Integer.valueOf(this.skip) }; + } + + // stub method + void endEntity() { + } + + // Adapted from: + // org.apache.xerces.impl.XMLEntityManager.startEntity() + private void createInitialReader() throws IOException, JasperException { + + // wrap this stream in RewindableInputStream + stream = new RewindableInputStream(stream); + + // perform auto-detect of encoding if necessary + if (encoding == null) { + // read first four bytes and determine encoding + final byte[] b4 = new byte[4]; + int count = 0; + for (; count<4; count++ ) { + b4[count] = (byte)stream.read(); + } + if (count == 4) { + Object [] encodingDesc = getEncodingName(b4, count); + encoding = (String)(encodingDesc[0]); + isBigEndian = (Boolean)(encodingDesc[1]); + + if (encodingDesc.length > 3) { + isBomPresent = (Boolean)(encodingDesc[2]); + skip = (Integer)(encodingDesc[3]); + } else { + isBomPresent = true; + skip = (Integer)(encodingDesc[2]); + } + + stream.reset(); + // Special case UTF-8 files with BOM created by Microsoft + // tools. It's more efficient to consume the BOM than make + // the reader perform extra checks. -Ac + if (count > 2 && encoding.equals("UTF-8")) { + int b0 = b4[0] & 0xFF; + int b1 = b4[1] & 0xFF; + int b2 = b4[2] & 0xFF; + if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) { + // ignore first three bytes... + stream.skip(3); + } + } + reader = createReader(stream, encoding, isBigEndian); + } else { + reader = createReader(stream, encoding, isBigEndian); + } + } + } + + // Adapted from: + // org.apache.xerces.impl.XMLEntityManager.createReader + /** + * Creates a reader capable of reading the given input stream in + * the specified encoding. + * + * @param inputStream The input stream. + * @param encoding The encoding name that the input stream is + * encoded using. If the user has specified that + * Java encoding names are allowed, then the + * encoding name may be a Java encoding name; + * otherwise, it is an ianaEncoding name. + * @param isBigEndian For encodings (like uCS-4), whose names cannot + * specify a byte order, this tells whether the order + * is bigEndian. null means unknown or not relevant. + * + * @return Returns a reader. + */ + private Reader createReader(InputStream inputStream, String encoding, + Boolean isBigEndian) + throws IOException, JasperException { + + // normalize encoding name + if (encoding == null) { + encoding = "UTF-8"; + } + + // try to use an optimized reader + String ENCODING = encoding.toUpperCase(Locale.ENGLISH); + if (ENCODING.equals("UTF-8")) { + return new UTF8Reader(inputStream, fBufferSize); + } + if (ENCODING.equals("US-ASCII")) { + return new ASCIIReader(inputStream, fBufferSize); + } + if (ENCODING.equals("ISO-10646-UCS-4")) { + if (isBigEndian != null) { + boolean isBE = isBigEndian.booleanValue(); + if (isBE) { + return new UCSReader(inputStream, UCSReader.UCS4BE); + } else { + return new UCSReader(inputStream, UCSReader.UCS4LE); + } + } else { + err.jspError("jsp.error.xml.encodingByteOrderUnsupported", + encoding); + } + } + if (ENCODING.equals("ISO-10646-UCS-2")) { + if (isBigEndian != null) { // sould never happen with this encoding... + boolean isBE = isBigEndian.booleanValue(); + if (isBE) { + return new UCSReader(inputStream, UCSReader.UCS2BE); + } else { + return new UCSReader(inputStream, UCSReader.UCS2LE); + } + } else { + err.jspError("jsp.error.xml.encodingByteOrderUnsupported", + encoding); + } + } + + // check for valid name + boolean validIANA = XMLChar.isValidIANAEncoding(encoding); + boolean validJava = XMLChar.isValidJavaEncoding(encoding); + if (!validIANA || (fAllowJavaEncodings && !validJava)) { + err.jspError("jsp.error.xml.encodingDeclInvalid", encoding); + // NOTE: AndyH suggested that, on failure, we use ISO Latin 1 + // because every byte is a valid ISO Latin 1 character. + // It may not translate correctly but if we failed on + // the encoding anyway, then we're expecting the content + // of the document to be bad. This will just prevent an + // invalid UTF-8 sequence to be detected. This is only + // important when continue-after-fatal-error is turned + // on. -Ac + encoding = "ISO-8859-1"; + } + + // try to use a Java reader + String javaEncoding = EncodingMap.getIANA2JavaMapping(ENCODING); + if (javaEncoding == null) { + if (fAllowJavaEncodings) { + javaEncoding = encoding; + } else { + err.jspError("jsp.error.xml.encodingDeclInvalid", encoding); + // see comment above. + javaEncoding = "ISO8859_1"; + } + } + return new InputStreamReader(inputStream, javaEncoding); + + } // createReader(InputStream,String, Boolean): Reader + + // Adapted from: + // org.apache.xerces.impl.XMLEntityManager.getEncodingName + /** + * Returns the IANA encoding name that is auto-detected from + * the bytes specified, with the endian-ness of that encoding where + * appropriate. + * + * @param b4 The first four bytes of the input. + * @param count The number of bytes actually read. + * @return a 2-element array: the first element, an IANA-encoding string, + * the second element a Boolean which is true iff the document is big + * endian, false if it's little-endian, and null if the distinction isn't + * relevant. + */ + private Object[] getEncodingName(byte[] b4, int count) { + + if (count < 2) { + return new Object[]{"UTF-8", null, Boolean.FALSE, Integer.valueOf(0)}; + } + + // UTF-16, with BOM + int b0 = b4[0] & 0xFF; + int b1 = b4[1] & 0xFF; + if (b0 == 0xFE && b1 == 0xFF) { + // UTF-16, big-endian + return new Object [] {"UTF-16BE", Boolean.TRUE, Integer.valueOf(2)}; + } + if (b0 == 0xFF && b1 == 0xFE) { + // UTF-16, little-endian + return new Object [] {"UTF-16LE", Boolean.FALSE, Integer.valueOf(2)}; + } + + // default to UTF-8 if we don't have enough bytes to make a + // good determination of the encoding + if (count < 3) { + return new Object [] {"UTF-8", null, Boolean.FALSE, Integer.valueOf(0)}; + } + + // UTF-8 with a BOM + int b2 = b4[2] & 0xFF; + if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) { + return new Object [] {"UTF-8", null, Integer.valueOf(3)}; + } + + // default to UTF-8 if we don't have enough bytes to make a + // good determination of the encoding + if (count < 4) { + return new Object [] {"UTF-8", null, Integer.valueOf(0)}; + } + + // other encodings + int b3 = b4[3] & 0xFF; + if (b0 == 0x00 && b1 == 0x00 && b2 == 0x00 && b3 == 0x3C) { + // UCS-4, big endian (1234) + return new Object [] {"ISO-10646-UCS-4", new Boolean(true), Integer.valueOf(4)}; + } + if (b0 == 0x3C && b1 == 0x00 && b2 == 0x00 && b3 == 0x00) { + // UCS-4, little endian (4321) + return new Object [] {"ISO-10646-UCS-4", new Boolean(false), Integer.valueOf(4)}; + } + if (b0 == 0x00 && b1 == 0x00 && b2 == 0x3C && b3 == 0x00) { + // UCS-4, unusual octet order (2143) + // REVISIT: What should this be? + return new Object [] {"ISO-10646-UCS-4", null, Integer.valueOf(4)}; + } + if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x00) { + // UCS-4, unusual octect order (3412) + // REVISIT: What should this be? + return new Object [] {"ISO-10646-UCS-4", null, Integer.valueOf(4)}; + } + if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) { + // UTF-16, big-endian, no BOM + // (or could turn out to be UCS-2... + // REVISIT: What should this be? + return new Object [] {"UTF-16BE", new Boolean(true), Integer.valueOf(4)}; + } + if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) { + // UTF-16, little-endian, no BOM + // (or could turn out to be UCS-2... + return new Object [] {"UTF-16LE", new Boolean(false), Integer.valueOf(4)}; + } + if (b0 == 0x4C && b1 == 0x6F && b2 == 0xA7 && b3 == 0x94) { + // EBCDIC + // a la xerces1, return CP037 instead of EBCDIC here + return new Object [] {"CP037", null, Integer.valueOf(4)}; + } + + // default encoding + return new Object [] {"UTF-8", null, Boolean.FALSE, Integer.valueOf(0)}; + + } + + // Adapted from: + // org.apache.xerces.impl.XMLEntityManager.EntityScanner.isExternal + /** Returns true if the current entity being scanned is external. */ + public boolean isExternal() { + return true; + } + + // Adapted from: + // org.apache.xerces.impl.XMLEntityManager.EntityScanner.peekChar + /** + * Returns the next character on the input. + *

+ * Note: The character is not consumed. + * + * @throws IOException Thrown if i/o error occurs. + * @throws EOFException Thrown on end of file. + */ + public int peekChar() throws IOException { + + // load more characters, if needed + if (fCurrentEntity.position == fCurrentEntity.count) { + load(0, true); + } + + // peek at character + int c = fCurrentEntity.ch[fCurrentEntity.position]; + + // return peeked character + if (fCurrentEntity.isExternal()) { + return c != '\r' ? c : '\n'; + } + else { + return c; + } + + } // peekChar():int + + // Adapted from: + // org.apache.xerces.impl.XMLEntityManager.EntityScanner.scanChar + /** + * Returns the next character on the input. + *

+ * Note: The character is consumed. + * + * @throws IOException Thrown if i/o error occurs. + * @throws EOFException Thrown on end of file. + */ + public int scanChar() throws IOException { + + // load more characters, if needed + if (fCurrentEntity.position == fCurrentEntity.count) { + load(0, true); + } + + // scan character + int c = fCurrentEntity.ch[fCurrentEntity.position++]; + boolean external = false; + if (c == '\n' || + (c == '\r' && (external = fCurrentEntity.isExternal()))) { + fCurrentEntity.lineNumber++; + fCurrentEntity.columnNumber = 1; + if (fCurrentEntity.position == fCurrentEntity.count) { + fCurrentEntity.ch[0] = (char)c; + load(1, false); + } + if (c == '\r' && external) { + if (fCurrentEntity.ch[fCurrentEntity.position++] != '\n') { + fCurrentEntity.position--; + } + c = '\n'; + } + } + + // return character that was scanned + fCurrentEntity.columnNumber++; + return c; + + } + + // Adapted from: + // org.apache.xerces.impl.XMLEntityManager.EntityScanner.scanName + /** + * Returns a string matching the Name production appearing immediately + * on the input as a symbol, or null if no Name string is present. + *

+ * Note: The Name characters are consumed. + *

+ * Note: The string returned must be a symbol. The + * SymbolTable can be used for this purpose. + * + * @throws IOException Thrown if i/o error occurs. + * @throws EOFException Thrown on end of file. + * + * @see SymbolTable + * @see XMLChar#isName + * @see XMLChar#isNameStart + */ + public String scanName() throws IOException { + + // load more characters, if needed + if (fCurrentEntity.position == fCurrentEntity.count) { + load(0, true); + } + + // scan name + int offset = fCurrentEntity.position; + if (XMLChar.isNameStart(fCurrentEntity.ch[offset])) { + if (++fCurrentEntity.position == fCurrentEntity.count) { + fCurrentEntity.ch[0] = fCurrentEntity.ch[offset]; + offset = 0; + if (load(1, false)) { + fCurrentEntity.columnNumber++; + String symbol = fSymbolTable.addSymbol(fCurrentEntity.ch, + 0, 1); + return symbol; + } + } + while (XMLChar.isName(fCurrentEntity.ch[fCurrentEntity.position])) { + if (++fCurrentEntity.position == fCurrentEntity.count) { + int length = fCurrentEntity.position - offset; + if (length == fBufferSize) { + // bad luck we have to resize our buffer + char[] tmp = new char[fBufferSize * 2]; + System.arraycopy(fCurrentEntity.ch, offset, + tmp, 0, length); + fCurrentEntity.ch = tmp; + fBufferSize *= 2; + } else { + System.arraycopy(fCurrentEntity.ch, offset, + fCurrentEntity.ch, 0, length); + } + offset = 0; + if (load(length, false)) { + break; + } + } + } + } + int length = fCurrentEntity.position - offset; + fCurrentEntity.columnNumber += length; + + // return name + String symbol = null; + if (length > 0) { + symbol = fSymbolTable.addSymbol(fCurrentEntity.ch, offset, length); + } + return symbol; + + } + + // Adapted from: + // org.apache.xerces.impl.XMLEntityManager.EntityScanner.scanLiteral + /** + * Scans a range of attribute value data, setting the fields of the + * XMLString structure, appropriately. + *

+ * Note: The characters are consumed. + *

+ * Note: This method does not guarantee to return + * the longest run of attribute value data. This method may return + * before the quote character due to reaching the end of the input + * buffer or any other reason. + *

+ * Note: The fields contained in the XMLString + * structure are not guaranteed to remain valid upon subsequent calls + * to the entity scanner. Therefore, the caller is responsible for + * immediately using the returned character data or making a copy of + * the character data. + * + * @param quote The quote character that signifies the end of the + * attribute value data. + * @param content The content structure to fill. + * + * @return Returns the next character on the input, if known. This + * value may be -1 but this does note designate + * end of file. + * + * @throws IOException Thrown if i/o error occurs. + * @throws EOFException Thrown on end of file. + */ + public int scanLiteral(int quote, XMLString content) + throws IOException { + + // load more characters, if needed + if (fCurrentEntity.position == fCurrentEntity.count) { + load(0, true); + } else if (fCurrentEntity.position == fCurrentEntity.count - 1) { + fCurrentEntity.ch[0] = fCurrentEntity.ch[fCurrentEntity.count - 1]; + load(1, false); + fCurrentEntity.position = 0; + } + + // normalize newlines + int offset = fCurrentEntity.position; + int c = fCurrentEntity.ch[offset]; + int newlines = 0; + boolean external = fCurrentEntity.isExternal(); + if (c == '\n' || (c == '\r' && external)) { + do { + c = fCurrentEntity.ch[fCurrentEntity.position++]; + if (c == '\r' && external) { + newlines++; + fCurrentEntity.lineNumber++; + fCurrentEntity.columnNumber = 1; + if (fCurrentEntity.position == fCurrentEntity.count) { + offset = 0; + fCurrentEntity.position = newlines; + if (load(newlines, false)) { + break; + } + } + if (fCurrentEntity.ch[fCurrentEntity.position] == '\n') { + fCurrentEntity.position++; + offset++; + } + /*** NEWLINE NORMALIZATION ***/ + else { + newlines++; + } + /***/ + } + else if (c == '\n') { + newlines++; + fCurrentEntity.lineNumber++; + fCurrentEntity.columnNumber = 1; + if (fCurrentEntity.position == fCurrentEntity.count) { + offset = 0; + fCurrentEntity.position = newlines; + if (load(newlines, false)) { + break; + } + } + /*** NEWLINE NORMALIZATION *** + if (fCurrentEntity.ch[fCurrentEntity.position] == '\r' + && external) { + fCurrentEntity.position++; + offset++; + } + /***/ + } + else { + fCurrentEntity.position--; + break; + } + } while (fCurrentEntity.position < fCurrentEntity.count - 1); + for (int i = offset; i < fCurrentEntity.position; i++) { + fCurrentEntity.ch[i] = '\n'; + } + int length = fCurrentEntity.position - offset; + if (fCurrentEntity.position == fCurrentEntity.count - 1) { + content.setValues(fCurrentEntity.ch, offset, length); + return -1; + } + } + + // scan literal value + while (fCurrentEntity.position < fCurrentEntity.count) { + c = fCurrentEntity.ch[fCurrentEntity.position++]; + if ((c == quote && + (!fCurrentEntity.literal || external)) + || c == '%' || !XMLChar.isContent(c)) { + fCurrentEntity.position--; + break; + } + } + int length = fCurrentEntity.position - offset; + fCurrentEntity.columnNumber += length - newlines; + content.setValues(fCurrentEntity.ch, offset, length); + + // return next character + if (fCurrentEntity.position != fCurrentEntity.count) { + c = fCurrentEntity.ch[fCurrentEntity.position]; + // NOTE: We don't want to accidentally signal the + // end of the literal if we're expanding an + // entity appearing in the literal. -Ac + if (c == quote && fCurrentEntity.literal) { + c = -1; + } + } + else { + c = -1; + } + return c; + + } + + /** + * Scans a range of character data up to the specified delimiter, + * setting the fields of the XMLString structure, appropriately. + *

+ * Note: The characters are consumed. + *

+ * Note: This assumes that the internal buffer is + * at least the same size, or bigger, than the length of the delimiter + * and that the delimiter contains at least one character. + *

+ * Note: This method does not guarantee to return + * the longest run of character data. This method may return before + * the delimiter due to reaching the end of the input buffer or any + * other reason. + *

+ * Note: The fields contained in the XMLString + * structure are not guaranteed to remain valid upon subsequent calls + * to the entity scanner. Therefore, the caller is responsible for + * immediately using the returned character data or making a copy of + * the character data. + * + * @param delimiter The string that signifies the end of the character + * data to be scanned. + * @param buffer The data structure to fill. + * + * @return Returns true if there is more data to scan, false otherwise. + * + * @throws IOException Thrown if i/o error occurs. + * @throws EOFException Thrown on end of file. + */ + public boolean scanData(String delimiter, XMLStringBuffer buffer) + throws IOException { + + boolean done = false; + int delimLen = delimiter.length(); + char charAt0 = delimiter.charAt(0); + boolean external = fCurrentEntity.isExternal(); + do { + + // load more characters, if needed + + if (fCurrentEntity.position == fCurrentEntity.count) { + load(0, true); + } + else if (fCurrentEntity.position >= fCurrentEntity.count - delimLen) { + System.arraycopy(fCurrentEntity.ch, fCurrentEntity.position, + fCurrentEntity.ch, 0, fCurrentEntity.count - fCurrentEntity.position); + load(fCurrentEntity.count - fCurrentEntity.position, false); + fCurrentEntity.position = 0; + } + if (fCurrentEntity.position >= fCurrentEntity.count - delimLen) { + // something must be wrong with the input: e.g., file ends an + // unterminated comment + int length = fCurrentEntity.count - fCurrentEntity.position; + buffer.append (fCurrentEntity.ch, fCurrentEntity.position, + length); + fCurrentEntity.columnNumber += fCurrentEntity.count; + fCurrentEntity.position = fCurrentEntity.count; + load(0,true); + return false; + } + + // normalize newlines + int offset = fCurrentEntity.position; + int c = fCurrentEntity.ch[offset]; + int newlines = 0; + if (c == '\n' || (c == '\r' && external)) { + do { + c = fCurrentEntity.ch[fCurrentEntity.position++]; + if (c == '\r' && external) { + newlines++; + fCurrentEntity.lineNumber++; + fCurrentEntity.columnNumber = 1; + if (fCurrentEntity.position == fCurrentEntity.count) { + offset = 0; + fCurrentEntity.position = newlines; + if (load(newlines, false)) { + break; + } + } + if (fCurrentEntity.ch[fCurrentEntity.position] == '\n') { + fCurrentEntity.position++; + offset++; + } + /*** NEWLINE NORMALIZATION ***/ + else { + newlines++; + } + } + else if (c == '\n') { + newlines++; + fCurrentEntity.lineNumber++; + fCurrentEntity.columnNumber = 1; + if (fCurrentEntity.position == fCurrentEntity.count) { + offset = 0; + fCurrentEntity.position = newlines; + fCurrentEntity.count = newlines; + if (load(newlines, false)) { + break; + } + } + } + else { + fCurrentEntity.position--; + break; + } + } while (fCurrentEntity.position < fCurrentEntity.count - 1); + for (int i = offset; i < fCurrentEntity.position; i++) { + fCurrentEntity.ch[i] = '\n'; + } + int length = fCurrentEntity.position - offset; + if (fCurrentEntity.position == fCurrentEntity.count - 1) { + buffer.append(fCurrentEntity.ch, offset, length); + return true; + } + } + + // iterate over buffer looking for delimiter + OUTER: while (fCurrentEntity.position < fCurrentEntity.count) { + c = fCurrentEntity.ch[fCurrentEntity.position++]; + if (c == charAt0) { + // looks like we just hit the delimiter + int delimOffset = fCurrentEntity.position - 1; + for (int i = 1; i < delimLen; i++) { + if (fCurrentEntity.position == fCurrentEntity.count) { + fCurrentEntity.position -= i; + break OUTER; + } + c = fCurrentEntity.ch[fCurrentEntity.position++]; + if (delimiter.charAt(i) != c) { + fCurrentEntity.position--; + break; + } + } + if (fCurrentEntity.position == delimOffset + delimLen) { + done = true; + break; + } + } + else if (c == '\n' || (external && c == '\r')) { + fCurrentEntity.position--; + break; + } + else if (XMLChar.isInvalid(c)) { + fCurrentEntity.position--; + int length = fCurrentEntity.position - offset; + fCurrentEntity.columnNumber += length - newlines; + buffer.append(fCurrentEntity.ch, offset, length); + return true; + } + } + int length = fCurrentEntity.position - offset; + fCurrentEntity.columnNumber += length - newlines; + if (done) { + length -= delimLen; + } + buffer.append (fCurrentEntity.ch, offset, length); + + // return true if string was skipped + } while (!done); + return !done; + + } + + // Adapted from: + // org.apache.xerces.impl.XMLEntityManager.EntityScanner.skipChar + /** + * Skips a character appearing immediately on the input. + *

+ * Note: The character is consumed only if it matches + * the specified character. + * + * @param c The character to skip. + * + * @return Returns true if the character was skipped. + * + * @throws IOException Thrown if i/o error occurs. + * @throws EOFException Thrown on end of file. + */ + public boolean skipChar(int c) throws IOException { + + // load more characters, if needed + if (fCurrentEntity.position == fCurrentEntity.count) { + load(0, true); + } + + // skip character + int cc = fCurrentEntity.ch[fCurrentEntity.position]; + if (cc == c) { + fCurrentEntity.position++; + if (c == '\n') { + fCurrentEntity.lineNumber++; + fCurrentEntity.columnNumber = 1; + } + else { + fCurrentEntity.columnNumber++; + } + return true; + } else if (c == '\n' && cc == '\r' && fCurrentEntity.isExternal()) { + // handle newlines + if (fCurrentEntity.position == fCurrentEntity.count) { + fCurrentEntity.ch[0] = (char)cc; + load(1, false); + } + fCurrentEntity.position++; + if (fCurrentEntity.ch[fCurrentEntity.position] == '\n') { + fCurrentEntity.position++; + } + fCurrentEntity.lineNumber++; + fCurrentEntity.columnNumber = 1; + return true; + } + + // character was not skipped + return false; + + } + + // Adapted from: + // org.apache.xerces.impl.XMLEntityManager.EntityScanner.skipSpaces + /** + * Skips space characters appearing immediately on the input. + *

+ * Note: The characters are consumed only if they are + * space characters. + * + * @return Returns true if at least one space character was skipped. + * + * @throws IOException Thrown if i/o error occurs. + * @throws EOFException Thrown on end of file. + * + * @see XMLChar#isSpace + */ + public boolean skipSpaces() throws IOException { + + // load more characters, if needed + if (fCurrentEntity.position == fCurrentEntity.count) { + load(0, true); + } + + // skip spaces + int c = fCurrentEntity.ch[fCurrentEntity.position]; + if (XMLChar.isSpace(c)) { + boolean external = fCurrentEntity.isExternal(); + do { + boolean entityChanged = false; + // handle newlines + if (c == '\n' || (external && c == '\r')) { + fCurrentEntity.lineNumber++; + fCurrentEntity.columnNumber = 1; + if (fCurrentEntity.position == fCurrentEntity.count - 1) { + fCurrentEntity.ch[0] = (char)c; + entityChanged = load(1, true); + if (!entityChanged) + // the load change the position to be 1, + // need to restore it when entity not changed + fCurrentEntity.position = 0; + } + if (c == '\r' && external) { + // REVISIT: Does this need to be updated to fix the + // #x0D ^#x0A newline normalization problem? -Ac + if (fCurrentEntity.ch[++fCurrentEntity.position] != '\n') { + fCurrentEntity.position--; + } + } + /*** NEWLINE NORMALIZATION *** + else { + if (fCurrentEntity.ch[fCurrentEntity.position + 1] == '\r' + && external) { + fCurrentEntity.position++; + } + } + /***/ + } + else { + fCurrentEntity.columnNumber++; + } + // load more characters, if needed + if (!entityChanged) + fCurrentEntity.position++; + if (fCurrentEntity.position == fCurrentEntity.count) { + load(0, true); + } + } while (XMLChar.isSpace(c = fCurrentEntity.ch[fCurrentEntity.position])); + return true; + } + + // no spaces were found + return false; + + } + + /** + * Skips the specified string appearing immediately on the input. + *

+ * Note: The characters are consumed only if they are + * space characters. + * + * @param s The string to skip. + * + * @return Returns true if the string was skipped. + * + * @throws IOException Thrown if i/o error occurs. + * @throws EOFException Thrown on end of file. + */ + public boolean skipString(String s) throws IOException { + + // load more characters, if needed + if (fCurrentEntity.position == fCurrentEntity.count) { + load(0, true); + } + + // skip string + final int length = s.length(); + for (int i = 0; i < length; i++) { + char c = fCurrentEntity.ch[fCurrentEntity.position++]; + if (c != s.charAt(i)) { + fCurrentEntity.position -= i + 1; + return false; + } + if (i < length - 1 && fCurrentEntity.position == fCurrentEntity.count) { + System.arraycopy(fCurrentEntity.ch, fCurrentEntity.count - i - 1, fCurrentEntity.ch, 0, i + 1); + // REVISIT: Can a string to be skipped cross an + // entity boundary? -Ac + if (load(i + 1, false)) { + fCurrentEntity.position -= i + 1; + return false; + } + } + } + fCurrentEntity.columnNumber += length; + return true; + + } + + // Adapted from: + // org.apache.xerces.impl.XMLEntityManager.EntityScanner.load + /** + * Loads a chunk of text. + * + * @param offset The offset into the character buffer to + * read the next batch of characters. + * @param changeEntity True if the load should change entities + * at the end of the entity, otherwise leave + * the current entity in place and the entity + * boundary will be signaled by the return + * value. + * + * @returns Returns true if the entity changed as a result of this + * load operation. + */ + final boolean load(int offset, boolean changeEntity) + throws IOException { + + // read characters + int length = fCurrentEntity.mayReadChunks? + (fCurrentEntity.ch.length - offset): + (DEFAULT_XMLDECL_BUFFER_SIZE); + int count = fCurrentEntity.reader.read(fCurrentEntity.ch, offset, + length); + + // reset count and position + boolean entityChanged = false; + if (count != -1) { + if (count != 0) { + fCurrentEntity.count = count + offset; + fCurrentEntity.position = offset; + } + } + + // end of this entity + else { + fCurrentEntity.count = offset; + fCurrentEntity.position = offset; + entityChanged = true; + if (changeEntity) { + endEntity(); + if (fCurrentEntity == null) { + throw new EOFException(); + } + // handle the trailing edges + if (fCurrentEntity.position == fCurrentEntity.count) { + load(0, false); + } + } + } + + return entityChanged; + + } + + // Adapted from: + // org.apache.xerces.impl.XMLEntityManager.RewindableInputStream + /** + * This class wraps the byte inputstreams we're presented with. + * We need it because java.io.InputStreams don't provide + * functionality to reread processed bytes, and they have a habit + * of reading more than one character when you call their read() + * methods. This means that, once we discover the true (declared) + * encoding of a document, we can neither backtrack to read the + * whole doc again nor start reading where we are with a new + * reader. + * + * This class allows rewinding an inputStream by allowing a mark + * to be set, and the stream reset to that position. The + * class assumes that it needs to read one character per + * invocation when it's read() method is inovked, but uses the + * underlying InputStream's read(char[], offset length) method--it + * won't buffer data read this way! + * + * @author Neil Graham, IBM + * @author Glenn Marcy, IBM + */ + private final class RewindableInputStream extends InputStream { + + private InputStream fInputStream; + private byte[] fData; + private int fStartOffset; + private int fEndOffset; + private int fOffset; + private int fLength; + private int fMark; + + public RewindableInputStream(InputStream is) { + fData = new byte[DEFAULT_XMLDECL_BUFFER_SIZE]; + fInputStream = is; + fStartOffset = 0; + fEndOffset = -1; + fOffset = 0; + fLength = 0; + fMark = 0; + } + + public void setStartOffset(int offset) { + fStartOffset = offset; + } + + public void rewind() { + fOffset = fStartOffset; + } + + public int read() throws IOException { + int b = 0; + if (fOffset < fLength) { + return fData[fOffset++] & 0xff; + } + if (fOffset == fEndOffset) { + return -1; + } + if (fOffset == fData.length) { + byte[] newData = new byte[fOffset << 1]; + System.arraycopy(fData, 0, newData, 0, fOffset); + fData = newData; + } + b = fInputStream.read(); + if (b == -1) { + fEndOffset = fOffset; + return -1; + } + fData[fLength++] = (byte)b; + fOffset++; + return b & 0xff; + } + + public int read(byte[] b, int off, int len) throws IOException { + int bytesLeft = fLength - fOffset; + if (bytesLeft == 0) { + if (fOffset == fEndOffset) { + return -1; + } + // better get some more for the voracious reader... + if (fCurrentEntity.mayReadChunks) { + return fInputStream.read(b, off, len); + } + int returnedVal = read(); + if (returnedVal == -1) { + fEndOffset = fOffset; + return -1; + } + b[off] = (byte)returnedVal; + return 1; + } + if (len < bytesLeft) { + if (len <= 0) { + return 0; + } + } + else { + len = bytesLeft; + } + if (b != null) { + System.arraycopy(fData, fOffset, b, off, len); + } + fOffset += len; + return len; + } + + public long skip(long n) + throws IOException + { + int bytesLeft; + if (n <= 0) { + return 0; + } + bytesLeft = fLength - fOffset; + if (bytesLeft == 0) { + if (fOffset == fEndOffset) { + return 0; + } + return fInputStream.skip(n); + } + if (n <= bytesLeft) { + fOffset += n; + return n; + } + fOffset += bytesLeft; + if (fOffset == fEndOffset) { + return bytesLeft; + } + n -= bytesLeft; + /* + * In a manner of speaking, when this class isn't permitting more + * than one byte at a time to be read, it is "blocking". The + * available() method should indicate how much can be read without + * blocking, so while we're in this mode, it should only indicate + * that bytes in its buffer are available; otherwise, the result of + * available() on the underlying InputStream is appropriate. + */ + return fInputStream.skip(n) + bytesLeft; + } + + public int available() throws IOException { + int bytesLeft = fLength - fOffset; + if (bytesLeft == 0) { + if (fOffset == fEndOffset) { + return -1; + } + return fCurrentEntity.mayReadChunks ? fInputStream.available() + : 0; + } + return bytesLeft; + } + + public void mark(int howMuch) { + fMark = fOffset; + } + + public void reset() { + fOffset = fMark; + } + + public boolean markSupported() { + return true; + } + + public void close() throws IOException { + if (fInputStream != null) { + fInputStream.close(); + fInputStream = null; + } + } + } // end of RewindableInputStream class + + // Adapted from: + // org.apache.xerces.impl.XMLDocumentScannerImpl.dispatch + private void scanXMLDecl() throws IOException, JasperException { + + if (skipString(" + *

+     * [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
+     * [24] VersionInfo ::= S 'version' Eq (' VersionNum ' | " VersionNum ")
+     * [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' |  "'" EncName "'" )
+     * [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*
+     * [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'")
+     *                 | ('"' ('yes' | 'no') '"'))
+     *
+     * [77] TextDecl ::= '<?xml' VersionInfo? EncodingDecl S? '?>'
+     * 
+ * + * @param scanningTextDecl True if a text declaration is to + * be scanned instead of an XML + * declaration. + */ + private void scanXMLDeclOrTextDecl(boolean scanningTextDecl) + throws IOException, JasperException { + + // scan decl + scanXMLDeclOrTextDecl(scanningTextDecl, fStrings); + fMarkupDepth--; + + // pseudo-attribute values + String encodingPseudoAttr = fStrings[1]; + + // set encoding on reader + if (encodingPseudoAttr != null) { + isEncodingSetInProlog = true; + encoding = encodingPseudoAttr; + } + } + + // Adapted from: + // org.apache.xerces.impl.XMLScanner.scanXMLDeclOrTextDecl + /** + * Scans an XML or text declaration. + *

+ *

+     * [23] XMLDecl ::= ''
+     * [24] VersionInfo ::= S 'version' Eq (' VersionNum ' | " VersionNum ")
+     * [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' |  "'" EncName "'" )
+     * [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*
+     * [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'")
+     *                 | ('"' ('yes' | 'no') '"'))
+     *
+     * [77] TextDecl ::= ''
+     * 
+ * + * @param scanningTextDecl True if a text declaration is to + * be scanned instead of an XML + * declaration. + * @param pseudoAttributeValues An array of size 3 to return the version, + * encoding and standalone pseudo attribute values + * (in that order). + * + * Note: This method uses fString, anything in it + * at the time of calling is lost. + */ + private void scanXMLDeclOrTextDecl(boolean scanningTextDecl, + String[] pseudoAttributeValues) + throws IOException, JasperException { + + // pseudo-attribute values + String version = null; + String encoding = null; + String standalone = null; + + // scan pseudo-attributes + final int STATE_VERSION = 0; + final int STATE_ENCODING = 1; + final int STATE_STANDALONE = 2; + final int STATE_DONE = 3; + int state = STATE_VERSION; + + boolean dataFoundForTarget = false; + boolean sawSpace = skipSpaces(); + while (peekChar() != '?') { + dataFoundForTarget = true; + String name = scanPseudoAttribute(scanningTextDecl, fString); + switch (state) { + case STATE_VERSION: { + if (name == fVersionSymbol) { + if (!sawSpace) { + reportFatalError(scanningTextDecl + ? "jsp.error.xml.spaceRequiredBeforeVersionInTextDecl" + : "jsp.error.xml.spaceRequiredBeforeVersionInXMLDecl", + null); + } + version = fString.toString(); + state = STATE_ENCODING; + if (!version.equals("1.0")) { + // REVISIT: XML REC says we should throw an error + // in such cases. + // some may object the throwing of fatalError. + err.jspError("jsp.error.xml.versionNotSupported", + version); + } + } else if (name == fEncodingSymbol) { + if (!scanningTextDecl) { + err.jspError("jsp.error.xml.versionInfoRequired"); + } + if (!sawSpace) { + reportFatalError(scanningTextDecl + ? "jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl" + : "jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl", + null); + } + encoding = fString.toString(); + state = scanningTextDecl ? STATE_DONE : STATE_STANDALONE; + } else { + if (scanningTextDecl) { + err.jspError("jsp.error.xml.encodingDeclRequired"); + } + else { + err.jspError("jsp.error.xml.versionInfoRequired"); + } + } + break; + } + case STATE_ENCODING: { + if (name == fEncodingSymbol) { + if (!sawSpace) { + reportFatalError(scanningTextDecl + ? "jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl" + : "jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl", + null); + } + encoding = fString.toString(); + state = scanningTextDecl ? STATE_DONE : STATE_STANDALONE; + // TODO: check encoding name; set encoding on + // entity scanner + } else if (!scanningTextDecl && name == fStandaloneSymbol) { + if (!sawSpace) { + err.jspError("jsp.error.xml.spaceRequiredBeforeStandalone"); + } + standalone = fString.toString(); + state = STATE_DONE; + if (!standalone.equals("yes") && !standalone.equals("no")) { + err.jspError("jsp.error.xml.sdDeclInvalid"); + } + } else { + err.jspError("jsp.error.xml.encodingDeclRequired"); + } + break; + } + case STATE_STANDALONE: { + if (name == fStandaloneSymbol) { + if (!sawSpace) { + err.jspError("jsp.error.xml.spaceRequiredBeforeStandalone"); + } + standalone = fString.toString(); + state = STATE_DONE; + if (!standalone.equals("yes") && !standalone.equals("no")) { + err.jspError("jsp.error.xml.sdDeclInvalid"); + } + } else { + err.jspError("jsp.error.xml.encodingDeclRequired"); + } + break; + } + default: { + err.jspError("jsp.error.xml.noMorePseudoAttributes"); + } + } + sawSpace = skipSpaces(); + } + // REVISIT: should we remove this error reporting? + if (scanningTextDecl && state != STATE_DONE) { + err.jspError("jsp.error.xml.morePseudoAttributes"); + } + + // If there is no data in the xml or text decl then we fail to report + // error for version or encoding info above. + if (scanningTextDecl) { + if (!dataFoundForTarget && encoding == null) { + err.jspError("jsp.error.xml.encodingDeclRequired"); + } + } else { + if (!dataFoundForTarget && version == null) { + err.jspError("jsp.error.xml.versionInfoRequired"); + } + } + + // end + if (!skipChar('?')) { + err.jspError("jsp.error.xml.xmlDeclUnterminated"); + } + if (!skipChar('>')) { + err.jspError("jsp.error.xml.xmlDeclUnterminated"); + + } + + // fill in return array + pseudoAttributeValues[0] = version; + pseudoAttributeValues[1] = encoding; + pseudoAttributeValues[2] = standalone; + } + + // Adapted from: + // org.apache.xerces.impl.XMLScanner.scanPseudoAttribute + /** + * Scans a pseudo attribute. + * + * @param scanningTextDecl True if scanning this pseudo-attribute for a + * TextDecl; false if scanning XMLDecl. This + * flag is needed to report the correct type of + * error. + * @param value The string to fill in with the attribute + * value. + * + * @return The name of the attribute + * + * Note: This method uses fStringBuffer2, anything in it + * at the time of calling is lost. + */ + public String scanPseudoAttribute(boolean scanningTextDecl, + XMLString value) + throws IOException, JasperException { + + String name = scanName(); + if (name == null) { + err.jspError("jsp.error.xml.pseudoAttrNameExpected"); + } + skipSpaces(); + if (!skipChar('=')) { + reportFatalError(scanningTextDecl ? + "jsp.error.xml.eqRequiredInTextDecl" + : "jsp.error.xml.eqRequiredInXMLDecl", + name); + } + skipSpaces(); + int quote = peekChar(); + if (quote != '\'' && quote != '"') { + reportFatalError(scanningTextDecl ? + "jsp.error.xml.quoteRequiredInTextDecl" + : "jsp.error.xml.quoteRequiredInXMLDecl" , + name); + } + scanChar(); + int c = scanLiteral(quote, value); + if (c != quote) { + fStringBuffer2.clear(); + do { + fStringBuffer2.append(value); + if (c != -1) { + if (c == '&' || c == '%' || c == '<' || c == ']') { + fStringBuffer2.append((char)scanChar()); + } + else if (XMLChar.isHighSurrogate(c)) { + scanSurrogates(fStringBuffer2); + } + else if (XMLChar.isInvalid(c)) { + String key = scanningTextDecl + ? "jsp.error.xml.invalidCharInTextDecl" + : "jsp.error.xml.invalidCharInXMLDecl"; + reportFatalError(key, Integer.toString(c, 16)); + scanChar(); + } + } + c = scanLiteral(quote, value); + } while (c != quote); + fStringBuffer2.append(value); + value.setValues(fStringBuffer2); + } + if (!skipChar(quote)) { + reportFatalError(scanningTextDecl ? + "jsp.error.xml.closeQuoteMissingInTextDecl" + : "jsp.error.xml.closeQuoteMissingInXMLDecl", + name); + } + + // return + return name; + + } + + // Adapted from: + // org.apache.xerces.impl.XMLScanner.scanPIData + /** + * Scans a processing data. This is needed to handle the situation + * where a document starts with a processing instruction whose + * target name starts with "xml". (e.g. xmlfoo) + * + * Note: This method uses fStringBuffer, anything in it + * at the time of calling is lost. + * + * @param target The PI target + * @param data The string to fill in with the data + */ + private void scanPIData(String target, XMLString data) + throws IOException, JasperException { + + // check target + if (target.length() == 3) { + char c0 = Character.toLowerCase(target.charAt(0)); + char c1 = Character.toLowerCase(target.charAt(1)); + char c2 = Character.toLowerCase(target.charAt(2)); + if (c0 == 'x' && c1 == 'm' && c2 == 'l') { + err.jspError("jsp.error.xml.reservedPITarget"); + } + } + + // spaces + if (!skipSpaces()) { + if (skipString("?>")) { + // we found the end, there is no data + data.clear(); + return; + } + else { + // if there is data there should be some space + err.jspError("jsp.error.xml.spaceRequiredInPI"); + } + } + + fStringBuffer.clear(); + // data + if (scanData("?>", fStringBuffer)) { + do { + int c = peekChar(); + if (c != -1) { + if (XMLChar.isHighSurrogate(c)) { + scanSurrogates(fStringBuffer); + } else if (XMLChar.isInvalid(c)) { + err.jspError("jsp.error.xml.invalidCharInPI", + Integer.toHexString(c)); + scanChar(); + } + } + } while (scanData("?>", fStringBuffer)); + } + data.setValues(fStringBuffer); + + } + + // Adapted from: + // org.apache.xerces.impl.XMLScanner.scanSurrogates + /** + * Scans surrogates and append them to the specified buffer. + *

+ * Note: This assumes the current char has already been + * identified as a high surrogate. + * + * @param buf The StringBuffer to append the read surrogates to. + * @returns True if it succeeded. + */ + private boolean scanSurrogates(XMLStringBuffer buf) + throws IOException, JasperException { + + int high = scanChar(); + int low = peekChar(); + if (!XMLChar.isLowSurrogate(low)) { + err.jspError("jsp.error.xml.invalidCharInContent", + Integer.toString(high, 16)); + return false; + } + scanChar(); + + // convert surrogates to supplemental character + int c = XMLChar.supplemental((char)high, (char)low); + + // supplemental character must be a valid XML character + if (!XMLChar.isValid(c)) { + err.jspError("jsp.error.xml.invalidCharInContent", + Integer.toString(c, 16)); + return false; + } + + // fill in the buffer + buf.append((char)high); + buf.append((char)low); + + return true; + + } + + // Adapted from: + // org.apache.xerces.impl.XMLScanner.reportFatalError + /** + * Convenience function used in all XML scanners. + */ + private void reportFatalError(String msgId, String arg) + throws JasperException { + err.jspError(msgId, arg); + } + +} + + diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLString.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLString.java new file mode 100644 index 000000000..10dc81a65 --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLString.java @@ -0,0 +1,197 @@ +/* + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 1999, International + * Business Machines, Inc., http://www.apache.org. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.jasper.xmlparser; + +/** + * This class is used as a structure to pass text contained in the underlying + * character buffer of the scanner. The offset and length fields allow the + * buffer to be re-used without creating new character arrays. + *

+ * Note: Methods that are passed an XMLString structure + * should consider the contents read-only and not make any modifications + * to the contents of the buffer. The method receiving this structure + * should also not modify the offset and length if this structure (or + * the values of this structure) are passed to another method. + *

+ * Note: Methods that are passed an XMLString structure + * are required to copy the information out of the buffer if it is to be + * saved for use beyond the scope of the method. The contents of the + * structure are volatile and the contents of the character buffer cannot + * be assured once the method that is passed this structure returns. + * Therefore, methods passed this structure should not save any reference + * to the structure or the character array contained in the structure. + * + * @author Eric Ye, IBM + * @author Andy Clark, IBM + * + * @version $Id: XMLString.java 467222 2006-10-24 03:17:11Z markt $ + */ +public class XMLString { + + // + // Data + // + + /** The character array. */ + public char[] ch; + + /** The offset into the character array. */ + public int offset; + + /** The length of characters from the offset. */ + public int length; + + // + // Constructors + // + + /** Default constructor. */ + public XMLString() { + } // () + + /** + * Constructs an XMLString structure preset with the specified + * values. + * + * @param ch The character array. + * @param offset The offset into the character array. + * @param length The length of characters from the offset. + */ + public XMLString(char[] ch, int offset, int length) { + setValues(ch, offset, length); + } // (char[],int,int) + + /** + * Constructs an XMLString structure with copies of the values in + * the given structure. + *

+ * Note: This does not copy the character array; + * only the reference to the array is copied. + * + * @param string The XMLString to copy. + */ + public XMLString(XMLString string) { + setValues(string); + } // (XMLString) + + // + // Public methods + // + + /** + * Initializes the contents of the XMLString structure with the + * specified values. + * + * @param ch The character array. + * @param offset The offset into the character array. + * @param length The length of characters from the offset. + */ + public void setValues(char[] ch, int offset, int length) { + this.ch = ch; + this.offset = offset; + this.length = length; + } // setValues(char[],int,int) + + /** + * Initializes the contents of the XMLString structure with copies + * of the given string structure. + *

+ * Note: This does not copy the character array; + * only the reference to the array is copied. + * + * @param s + */ + public void setValues(XMLString s) { + setValues(s.ch, s.offset, s.length); + } // setValues(XMLString) + + /** Resets all of the values to their defaults. */ + public void clear() { + this.ch = null; + this.offset = 0; + this.length = -1; + } // clear() + + /** + * Returns true if the contents of this XMLString structure and + * the specified array are equal. + * + * @param ch The character array. + * @param offset The offset into the character array. + * @param length The length of characters from the offset. + */ + public boolean equals(char[] ch, int offset, int length) { + if (ch == null) { + return false; + } + if (this.length != length) { + return false; + } + + for (int i=0; i 0 ? new String(ch, offset, length) : ""; + } // toString():String + +} // class XMLString diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLStringBuffer.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLStringBuffer.java new file mode 100644 index 000000000..93e6ebcfb --- /dev/null +++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLStringBuffer.java @@ -0,0 +1,176 @@ +/* + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 1999, International + * Business Machines, Inc., http://www.apache.org. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.jasper.xmlparser; + +/** + * XMLString is a structure used to pass character arrays. However, + * XMLStringBuffer is a buffer in which characters can be appended + * and extends XMLString so that it can be passed to methods + * expecting an XMLString object. This is a safe operation because + * it is assumed that any callee will not modify + * the contents of the XMLString structure. + *

+ * The contents of the string are managed by the string buffer. As + * characters are appended, the string buffer will grow as needed. + *

+ * Note: Never set the ch, + * offset, and length fields directly. + * These fields are managed by the string buffer. In order to reset + * the buffer, call clear(). + * + * @author Andy Clark, IBM + * @author Eric Ye, IBM + * + * @version $Id: XMLStringBuffer.java 467222 2006-10-24 03:17:11Z markt $ + */ +public class XMLStringBuffer + extends XMLString { + + // + // Constants + // + + /** Default buffer size (32). */ + public static final int DEFAULT_SIZE = 32; + + // + // Constructors + // + + /** + * + */ + public XMLStringBuffer() { + this(DEFAULT_SIZE); + } // () + + /** + * + * + * @param size + */ + public XMLStringBuffer(int size) { + ch = new char[size]; + } // (int) + + /** Constructs a string buffer from a char. */ + public XMLStringBuffer(char c) { + this(1); + append(c); + } // (char) + + /** Constructs a string buffer from a String. */ + public XMLStringBuffer(String s) { + this(s.length()); + append(s); + } // (String) + + /** Constructs a string buffer from the specified character array. */ + public XMLStringBuffer(char[] ch, int offset, int length) { + this(length); + append(ch, offset, length); + } // (char[],int,int) + + /** Constructs a string buffer from the specified XMLString. */ + public XMLStringBuffer(XMLString s) { + this(s.length); + append(s); + } // (XMLString) + + // + // Public methods + // + + /** Clears the string buffer. */ + public void clear() { + offset = 0; + length = 0; + } + + /** + * append + * + * @param c + */ + public void append(char c) { + if (this.length + 1 > this.ch.length) { + int newLength = this.ch.length*2; + if (newLength < this.ch.length + DEFAULT_SIZE) + newLength = this.ch.length + DEFAULT_SIZE; + char[] newch = new char[newLength]; + System.arraycopy(this.ch, 0, newch, 0, this.length); + this.ch = newch; + } + this.ch[this.length] = c; + this.length++; + } // append(char) + + /** + * append + * + * @param s + */ + public void append(String s) { + int length = s.length(); + if (this.length + length > this.ch.length) { + int newLength = this.ch.length*2; + if (newLength < this.length + length + DEFAULT_SIZE) + newLength = this.ch.length + length + DEFAULT_SIZE; + char[] newch = new char[newLength]; + System.arraycopy(this.ch, 0, newch, 0, this.length); + this.ch = newch; + } + s.getChars(0, length, this.ch, this.length); + this.length += length; + } // append(String) + + /** + * append + * + * @param ch + * @param offset + * @param length + */ + public void append(char[] ch, int offset, int length) { + if (this.length + length > this.ch.length) { + char[] newch = new char[this.ch.length + length + DEFAULT_SIZE]; + System.arraycopy(this.ch, 0, newch, 0, this.length); + this.ch = newch; + } + System.arraycopy(ch, offset, this.ch, this.length, length); + this.length += length; + } // append(char[],int,int) + + /** + * append + * + * @param s + */ + public void append(XMLString s) { + append(s.ch, s.offset, s.length); + } // append(XMLString) + +} // class XMLStringBuffer