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; + } +}