Index: src/org/python/util/PyServlet.java
===================================================================
--- src/org/python/util/PyServlet.java (revision 3911)
+++ src/org/python/util/PyServlet.java (working copy)
@@ -1,13 +1,28 @@
package org.python.util;
-import java.io.*;
-import java.util.*;
-import javax.servlet.*;
-import javax.servlet.http.*;
-import org.python.core.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import org.python.core.Py;
+import org.python.core.PyException;
+import org.python.core.PyObject;
+import org.python.core.PyString;
+import org.python.core.PySystemState;
+
+
/**
* This servlet is used to re-serve Jython servlets. It stores
* bytecode for Jython servlets and re-uses it if the underlying .py
@@ -58,36 +73,57 @@
*
*
*/
+public class PyServlet extends HttpServlet implements ServletContextListener {
+
+ private static final String INIT_ATTR = "__jython_initialized__";
-public class PyServlet extends HttpServlet {
private PythonInterpreter interp;
private Hashtable cache = new Hashtable();
private String rootPath;
+ public void contextInitialized(ServletContextEvent sce) {
+ init(new Properties(), sce.getServletContext());
+ }
+ public void contextDestroyed(ServletContextEvent sce) {}
+
public void init() {
- rootPath = getServletContext().getRealPath("/");
- if (!rootPath.endsWith(File.separator))
+ Properties props = new Properties();
+ // Config parameters
+ Enumeration e = getInitParameterNames();
+ while (e.hasMoreElements()) {
+ String name = (String) e.nextElement();
+ props.put(name, getInitParameter(name));
+ }
+ init(props, getServletContext());
+ }
+
+ /**
+ * PyServlet's initialization can be performed as a ServletContextListener or as a regular
+ * servlet, and this is the shared init code. If both initializations are used in a single
+ * context, this code only runs once.
+ */
+ private void init(Properties props, ServletContext context) {
+ if (context.getAttribute(INIT_ATTR) != null) {
+ System.err.println("Jython has already been initialized as a ServletContextListener " +
+ "or HttpServlet elsewhere in this context, not initializing again");
+ return;
+ }
+ context.setAttribute(INIT_ATTR, "true");
+ rootPath = context.getRealPath("/");
+ if (!rootPath.endsWith(File.separator)) {
rootPath += File.separator;
+ }
- Properties props = new Properties();
Properties baseProps = PySystemState.getBaseProperties();
// Context parameters
- ServletContext context = getServletContext();
Enumeration e = context.getInitParameterNames();
while (e.hasMoreElements()) {
String name = (String) e.nextElement();
props.put(name, context.getInitParameter(name));
}
- // Config parameters
- e = getInitParameterNames();
- while (e.hasMoreElements()) {
- String name = (String) e.nextElement();
- props.put(name, getInitParameter(name));
- }
-
if(props.getProperty("python.home") == null
&& baseProps.getProperty("python.home") == null) {
props.put("python.home", rootPath + "WEB-INF" +
Index: src/org/python/util/PyFilter.java
===================================================================
--- src/org/python/util/PyFilter.java (revision 0)
+++ src/org/python/util/PyFilter.java (revision 0)
@@ -0,0 +1,190 @@
+package org.python.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.python.core.Py;
+import org.python.core.PyException;
+import org.python.core.PyObject;
+import org.python.core.PySystemState;
+import org.python.util.PythonInterpreter;
+
+/**
+ *
+ * Enables you to write Jython modules that inherit from javax.servlet.Filter
, and
+ * to insert them in your servlet container's filter chain, like any Java Filter
.
+ *
+ *
+ * Example: + * + *
+ * /WEB-INF/filters/HeaderFilter.py: + * + *
+ * from javax.servlet import Filter + * + * # Module must contain a class with the same name as the module + * # which in turn must implement javax.servlet.Filter + * class HeaderFilter (Filter): + * def init(self, config): self.header = config.getInitParameter('header') + * + * def destroy(self): pass + * + * def doFilter(self, request, response, chain): + * response.setHeader(self.header, "Yup") + * chain.doFilter(request, response) + *+ * + *
+ * web.xml: + *
+ * + *+ * <!-- PyFilter depends on PyServlet --> + * <servlet> + * <servlet-name>PyServlet</servlet-name> + * <servlet-class>org.python.util.PyServlet</servlet-class> + * <load-on-startup>1</load-on-startup> + * </servlet> + * + * <!-- Declare a uniquely-named PyFilter --> + * <filter> + * <filter-name>HeaderFilter</filter-name> + * <filter-class>org.jython.util.PyFilter</filter-class> + * + * <!-- The special param __filter__ gives the context-relative path to the Jython source file --> + * <init-param> + * <param-name>__filter__</param-name> + * <param-value>/WEB-INF/pyfilter/HeaderFilter.py</param-value> + * </init-param> + * + * <!-- Other params are passed on the the Jython filter --> + * <init-param> + * <param-name>header</param-name> + * <param-value>X-LookMaNoJava</param-value> + * </init-param> + * </filter> + * <filter-mapping> + * <filter-name>HeaderFilter</filter-name> + * <url-pattern>/*</url-pattern> + * </filter-mapping> + *+ * + *
+ * PyFilter depends on initialization code from PyServlet being run. If PyServlet is used to serve + * pages, this code will be executed and PyFilter will work properly. However, if you aren't using + * PyServlet, and wouldn't like to include it as a servlet that isn't serving pages, PyServlet's + * initialization code can be invoked as a ServletContextListener instead of as an HttpServlet. Use + * the following in web.xml instead of a servlet tag: + * + *
+ * <listener> + * <listener-class>org.python.util.PyServlet</listener-class> + * <load-on-startup>1</load-on-startup> + * </listener> + *+ * + */ +public class PyFilter implements Filter { + + private static final String FILTER_PATH_PARAM = "__filter__"; + + private PythonInterpreter interp; + + FilterConfig filterConfig; + + private File filterSource; + + Filter cachedFilter; + + long timeLoaded = -1; + + public void doFilter(final ServletRequest request, + final ServletResponse response, + final FilterChain chain) throws IOException, ServletException { + request.setAttribute("pyfilter", this); + getFilter().doFilter(request, response, chain); + } + + public void init(final FilterConfig filterConfig) throws ServletException { + this.filterConfig = filterConfig; + final String filterPath = filterConfig.getInitParameter(FILTER_PATH_PARAM); + if (filterPath == null) { + throw new ServletException("Missing required param '" + + FILTER_PATH_PARAM + "'"); + } + filterSource = new File(getRealPath(filterConfig.getServletContext(), filterPath)); + if (!filterSource.exists()) { + throw new ServletException(filterSource.getAbsolutePath() + " does not exist."); + } + interp = new PythonInterpreter(null, new PySystemState()); + interp.set("__file__", filterSource.getAbsolutePath()); + interp.exec("import sys; sys.path.append('" + + getRealPath(filterConfig.getServletContext(), "/WEB-INF/jython/") + "')\n"); + } + + private String getRealPath(ServletContext context, final String appPath) { + final String realPath = context.getRealPath(appPath); + // This madness seems to be necessary on Windows + return realPath.replaceAll("\\\\", "/"); + } + + private Filter getFilter() throws ServletException, IOException { + if (cachedFilter == null || filterSource.lastModified() > timeLoaded) { + return loadFilter(); + } + return cachedFilter; + } + + private static final Pattern FIND_NAME = Pattern.compile("([^/]+)\\.py$"); + + private Filter loadFilter() throws ServletException, IOException { + cachedFilter = null; + try { + interp.execfile(filterSource.getAbsolutePath()); + final Matcher m = FIND_NAME.matcher(filterSource.getName()); + if (!m.find()) { + throw new ServletException("I can't guess the name of the filter class from " + + filterSource); + } + final String className = m.group(1); + final PyObject cls = interp.get(className); + if (cls == null) { + throw new ServletException("No class " + className + " in " + filterSource); + } + final Object pythonFilter = cls.__call__().__tojava__(Filter.class); + if (pythonFilter == Py.NoConversion) { + throw new ServletException(cls + " must extend javax.servlet.Filter"); + } + cachedFilter = (Filter)pythonFilter; + cachedFilter.init(filterConfig); + timeLoaded = new Date().getTime(); + } catch (final PyException e) { + throw new ServletException(e); + } + return cachedFilter; + } + + public void destroy() { + if (cachedFilter != null) { + cachedFilter.destroy(); + } + cachedFilter = null; + if (interp != null) { + interp.cleanup(); + } + interp = null; + filterSource = null; + } +}