You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by mu...@apache.org on 2009/09/28 02:43:39 UTC

svn commit: r819435 [7/23] - in /struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper: ./ compiler/ compiler/tagplugin/ el/ runtime/ security/ servlet/ tagplugins/ tagplugins/jstl/ tagplugins/jstl/core/ util/ xmlparser/

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspDocumentParser.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspDocumentParser.java?rev=819435&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspDocumentParser.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspDocumentParser.java Mon Sep 28 00:43:34 2009
@@ -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 <jsp:attribute> action, if the
+                // action's 'trim' attribute is set to TRUE (default).
+                // In addition, any textual nodes in the <jsp:attribute> that
+                // have only white space are dropped from the document, with
+                // the exception of leading and trailing white-space-only
+                // textual nodes in a <jsp:attribute> 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;
+    }
+}

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspReader.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspReader.java?rev=819435&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspReader.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspReader.java Mon Sep 28 00:43:34 2009
@@ -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 <strong>true</strong> is one is found, the current position
+     *         in stream is positioned after the search string, <strong>
+     *               false</strong> 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("</" + tagName))
+            return false;
+        skipSpaces();
+        if (nextChar() == '>')
+            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 <code>Mark</code> instance (positioned immediately
+     *         before the search string) if found, <strong>null</strong>
+     *         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 <code>Mark</code> instance (positioned immediately
+     *         before the search string) if found, <strong>null</strong>
+     *         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 (</tag>) to match.
+     * @return A non-null <code>Mark</code> instance (positioned immediately
+     *               before the ETag) if found, <strong>null</strong> otherwise.
+     */
+    Mark skipUntilETag(String tag) throws JasperException {
+        Mark ret = skipUntil("</" + tag);
+        if (ret != null) {
+            skipSpaces();
+            if (nextChar() != '>')
+                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 <strong>true</strong> 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 =, &gt;, &lt;, ", and ' or any
+     * any space character as defined by <code>isSpace</code>.
+     *
+     * @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;
+    }
+}
+

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java?rev=819435&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java Mon Sep 28 00:43:34 2009
@@ -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",
+					       "<none>"));
+	    }
+        }
+
+        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<String, JspServletWrapper> jsps = new ConcurrentHashMap<String, JspServletWrapper>();
+ 
+
+    // ------------------------------------------------------ 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<urls.length; i++) {
+                        if (jndiUrl == null
+                                && urls[i].toString().startsWith("jndi:") ) {
+                            jndiUrl = urls[i].toString() + "-";
+                        }
+                        if (jarUrl == null
+                                && urls[i].toString().startsWith("jar:jndi:")
+                                ) {
+                            jarUrl = urls[i].toString();
+                            jarUrl = jarUrl.substring(0,jarUrl.length() - 2);
+                            jarUrl = jarUrl.substring(0,
+                                     jarUrl.lastIndexOf('/')) + "/-";
+                        }
+                    }
+                    if (jarUrl != null) {
+                        permissionCollection.add(
+                                new FilePermission(jarUrl,"read"));
+                        permissionCollection.add(
+                                new FilePermission(jarUrl.substring(4),"read"));
+                    }
+                    if (jndiUrl != null)
+                        permissionCollection.add(
+                                new FilePermission(jndiUrl,"read") );
+                }
+            } catch(Exception e) {
+                context.log("Security Init for context failed",e);
+            }
+        }
+    }
+
+
+}