From: <cg...@us...> - 2009-06-22 08:27:12
|
Revision: 6492 http://jython.svn.sourceforge.net/jython/?rev=6492&view=rev Author: cgroves Date: 2009-06-22 08:27:10 +0000 (Mon, 22 Jun 2009) Log Message: ----------- Add jfeinberg's PyFilter from #1859477. Still need to test this in an actual ServletContainer Modified Paths: -------------- trunk/jython/src/org/python/util/PyServlet.java trunk/jython/tests/modjy/java/com/xhaus/modjy/ModjyTestBase.java trunk/jython/tests/modjy/java/org/python/util/PyServletTest.java Added Paths: ----------- trunk/jython/Lib/test/pyservlet/filter.py trunk/jython/src/org/python/util/PyFilter.java trunk/jython/tests/modjy/java/org/python/util/PyFilterTest.java Added: trunk/jython/Lib/test/pyservlet/filter.py =================================================================== --- trunk/jython/Lib/test/pyservlet/filter.py (rev 0) +++ trunk/jython/Lib/test/pyservlet/filter.py 2009-06-22 08:27:10 UTC (rev 6492) @@ -0,0 +1,9 @@ +from javax.servlet import Filter + +class filter(Filter): + def init(self, config): + self.header = config.getInitParameter('header') + + def doFilter(self, req, resp, chain): + resp.setHeader(self.header, "Yup") + chain.doFilter(req, resp) Added: trunk/jython/src/org/python/util/PyFilter.java =================================================================== --- trunk/jython/src/org/python/util/PyFilter.java (rev 0) +++ trunk/jython/src/org/python/util/PyFilter.java 2009-06-22 08:27:10 UTC (rev 6492) @@ -0,0 +1,158 @@ +package org.python.util; + +import java.io.File; +import java.io.IOException; +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.PyException; +import org.python.core.PySystemState; +import org.python.util.PythonInterpreter; + +/** + * Enables you to write Jython modules that inherit from <code>javax.servlet.Filter</code>, and to + * insert them in your servlet container's filter chain, like any Java <code>Filter</code>. + * + * <p> + * Example: + * + * <p> + * <b>/WEB-INF/filters/HeaderFilter.py:</b> + * + * <pre> + * 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 doFilter(self, request, response, chain): + * response.setHeader(self.header, "Yup") + * chain.doFilter(request, response) + * </pre> + * + * <p> + * <b>web.xml:</b> + * </p> + * + * <pre> + * <!-- 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> + * </pre> + * + * <p> + * 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, the initialization code can be invoked as a ServletContextListener instead of as an + * HttpServlet. Use the following in web.xml instead of a servlet tag: + * + * <pre> + * <listener> + * <listener-class>org.python.util.PyServlet</listener-class> + * <load-on-startup>1</load-on-startup> + * </listener> + * </pre> + * + */ +public class PyFilter implements Filter { + public static final String FILTER_PATH_PARAM = "__filter__"; + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + request.setAttribute("pyfilter", this); + getFilter().doFilter(request, response, chain); + } + + public void init(FilterConfig config) throws ServletException { + if (config.getServletContext().getAttribute(PyServlet.INIT_ATTR) == null) { + throw new ServletException("PyServlet has not been initialized, either as a servlet " + + "or as context listener. This must happen before PyFilter is initialized."); + } + this.config = config; + String filterPath = config.getInitParameter(FILTER_PATH_PARAM); + if (filterPath == null) { + throw new ServletException("Missing required param '" + FILTER_PATH_PARAM + "'"); + } + source = new File(getRealPath(config.getServletContext(), filterPath)); + if (!source.exists()) { + throw new ServletException(source.getAbsolutePath() + " does not exist."); + } + interp = new PythonInterpreter(null, new PySystemState()); + } + + private String getRealPath(ServletContext context, String appPath) { + String realPath = context.getRealPath(appPath); + // This madness seems to be necessary on Windows + return realPath.replaceAll("\\\\", "/"); + } + + private Filter getFilter() throws ServletException, IOException { + if (cached == null || source.lastModified() > loadedMtime) { + return loadFilter(); + } + return cached; + } + + private Filter loadFilter() throws ServletException, IOException { + loadedMtime = source.lastModified(); + cached = PyServlet.createInstance(interp, source, Filter.class); + try { + cached.init(config); + } catch (PyException e) { + throw new ServletException(e); + } + return cached; + } + + public void destroy() { + if (cached != null) { + cached.destroy(); + } + if (interp != null) { + interp.cleanup(); + } + } + + private PythonInterpreter interp; + + private FilterConfig config; + + private File source; + + private Filter cached; + + private long loadedMtime; +} Modified: trunk/jython/src/org/python/util/PyServlet.java =================================================================== --- trunk/jython/src/org/python/util/PyServlet.java 2009-06-22 06:48:53 UTC (rev 6491) +++ trunk/jython/src/org/python/util/PyServlet.java 2009-06-22 08:27:10 UTC (rev 6492) @@ -5,8 +5,12 @@ import java.util.Enumeration; import java.util.Map; import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -37,7 +41,6 @@ * print >>out, "</body>" * print >>out, "</html>" * out.close() - * return * </pre> * * in web.xml for the PyServlet context: @@ -59,47 +62,76 @@ * * </pre> */ -public class PyServlet extends HttpServlet { +public class PyServlet extends HttpServlet implements ServletContextListener { + + public static final String SKIP_INIT_NAME = "skip_jython_initialization"; + + protected static final String INIT_ATTR = "__jython_initialized__"; + + public void contextInitialized(ServletContextEvent event) { + init(new Properties(), event.getServletContext(), "a ServletContextListener", true); + } + + public void contextDestroyed(ServletContextEvent event) {} + @Override public void init() { - rootPath = getServletContext().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(); + Enumeration<?> 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" + File.separator + "lib"); + init(props, getServletContext(), "the servlet " + getServletConfig().getServletName(), + getServletConfig().getInitParameter(SKIP_INIT_NAME) != null); + } + + /** + * 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, the system state initialization code only runs once. + */ + private void init(Properties props, ServletContext context, String initializerName, + boolean initialize) { + rootPath = context.getRealPath("/"); + if (!rootPath.endsWith(File.separator)) { + rootPath += File.separator; } - - PySystemState.initialize(baseProps, props, new String[0]); + if (context.getAttribute(INIT_ATTR) != null) { + if (initialize) { + System.err.println("Jython has already been initialized by " + + context.getAttribute(INIT_ATTR) + + " in this context, not initializing for " + initializerName + ". Add " + + SKIP_INIT_NAME + + " to as an init param to this servlet's configuration to indicate this " + + "is expected."); + } + } else if (initialize) { + context.setAttribute(INIT_ATTR, initializerName); + Properties baseProps = PySystemState.getBaseProperties(); + // Context parameters + Enumeration<?> e = context.getInitParameterNames(); + while (e.hasMoreElements()) { + String name = (String)e.nextElement(); + props.put(name, context.getInitParameter(name)); + } + if (props.getProperty("python.home") == null + && baseProps.getProperty("python.home") == null) { + props.put("python.home", rootPath + "WEB-INF" + File.separator + "lib"); + } + PySystemState.initialize(baseProps, props, new String[0]); + PySystemState.add_package("javax.servlet"); + PySystemState.add_package("javax.servlet.http"); + PySystemState.add_package("javax.servlet.jsp"); + PySystemState.add_package("javax.servlet.jsp.tagext"); + PySystemState.add_classdir(rootPath + "WEB-INF" + File.separator + "classes"); + PySystemState.add_extdir(rootPath + "WEB-INF" + File.separator + "lib", true); + } reset(); + } - PySystemState.add_package("javax.servlet"); - PySystemState.add_package("javax.servlet.http"); - PySystemState.add_package("javax.servlet.jsp"); - PySystemState.add_package("javax.servlet.jsp.tagext"); - PySystemState.add_classdir(rootPath + "WEB-INF" + File.separator + "classes"); - - PySystemState.add_extdir(rootPath + "WEB-INF" + File.separator + "lib", true); - } - @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException @@ -154,40 +186,44 @@ { File file = new File(path); - // Extract servlet name from path (strip ".../" and ".py") - int start = path.lastIndexOf(File.separator); - if (start < 0) { - start = 0; - } else { - start++; + HttpServlet servlet = createInstance(interp, file, HttpServlet.class); + try { + servlet.init(getServletConfig()); + } catch (PyException e) { + throw new ServletException(e); } - int end = path.lastIndexOf('.'); - if (end < 0 || end <= start) { - end = path.length(); + cache.put(path, new CacheEntry(servlet, file.lastModified())); + return servlet; + } + + protected static <T> T createInstance(PythonInterpreter interp, File file, Class<T> type) + throws ServletException { + Matcher m = FIND_NAME.matcher(file.getName()); + if (!m.find()) { + throw new ServletException("I can't guess the name of the class from " + + file.getAbsolutePath()); } - String name = path.substring(start, end); - - HttpServlet servlet; + String name = m.group(1); try { - interp.set("__file__", path); - interp.execfile(path); + interp.set("__file__", file.getAbsolutePath()); + interp.execfile(file.getAbsolutePath()); PyObject cls = interp.get(name); if (cls == null) { throw new ServletException("No callable (class or function) named " + name + " in " - + path); + + file.getAbsolutePath()); } PyObject pyServlet = cls.__call__(); - Object o = pyServlet.__tojava__(HttpServlet.class); + Object o = pyServlet.__tojava__(type); if (o == Py.NoConversion) { - throw new ServletException("The value from " + name + " must extend HttpServlet"); + throw new ServletException("The value from " + name + " must extend " + + type.getSimpleName()); } - servlet = (HttpServlet)o; - servlet.init(getServletConfig()); + @SuppressWarnings("unchecked") + T asT = (T)o; + return asT; } catch (PyException e) { throw new ServletException(e); } - cache.put(path, new CacheEntry(servlet, file.lastModified())); - return servlet; } private void destroyCache() { @@ -207,6 +243,8 @@ } } + private static final Pattern FIND_NAME = Pattern.compile("([^/]+)\\.py$"); + private PythonInterpreter interp; private String rootPath; private Map<String, CacheEntry> cache = Generic.map(); Modified: trunk/jython/tests/modjy/java/com/xhaus/modjy/ModjyTestBase.java =================================================================== --- trunk/jython/tests/modjy/java/com/xhaus/modjy/ModjyTestBase.java 2009-06-22 06:48:53 UTC (rev 6491) +++ trunk/jython/tests/modjy/java/com/xhaus/modjy/ModjyTestBase.java 2009-06-22 08:27:10 UTC (rev 6492) @@ -20,22 +20,20 @@ package com.xhaus.modjy; -import junit.framework.*; +import junit.framework.TestSuite; -import com.mockrunner.servlet.BasicServletTestCaseAdapter; -import com.mockrunner.mock.web.WebMockObjectFactory; -import com.mockrunner.mock.web.MockServletConfig; -import com.mockrunner.mock.web.MockServletContext; -import com.mockrunner.mock.web.MockHttpServletRequest; -import com.mockrunner.mock.web.MockHttpServletResponse; - import org.jdom.output.XMLOutputter; - import org.python.core.PyObject; +import org.python.util.PyFilterTest; import org.python.util.PyServletTest; import org.python.util.PythonInterpreter; -import com.xhaus.modjy.ModjyJServlet; +import com.mockrunner.mock.web.MockHttpServletRequest; +import com.mockrunner.mock.web.MockHttpServletResponse; +import com.mockrunner.mock.web.MockServletConfig; +import com.mockrunner.mock.web.MockServletContext; +import com.mockrunner.mock.web.WebMockObjectFactory; +import com.mockrunner.servlet.BasicServletTestCaseAdapter; /** * @@ -274,6 +272,7 @@ suite.addTestSuite(ModjyTestWebInf.class); suite.addTestSuite(ModjyTestWSGIStreams.class); suite.addTestSuite(PyServletTest.class); + suite.addTestSuite(PyFilterTest.class); junit.textui.TestRunner.run(suite); } Added: trunk/jython/tests/modjy/java/org/python/util/PyFilterTest.java =================================================================== --- trunk/jython/tests/modjy/java/org/python/util/PyFilterTest.java (rev 0) +++ trunk/jython/tests/modjy/java/org/python/util/PyFilterTest.java 2009-06-22 08:27:10 UTC (rev 6492) @@ -0,0 +1,30 @@ +package org.python.util; + +import javax.servlet.http.HttpServletResponse; + +import com.mockrunner.mock.web.MockFilterConfig; + +public class PyFilterTest extends PyServletTest { + @Override + protected void setUp() throws Exception { + super.setUp(); + setDoChain(true); + } + + public void testFilter() { + getWebMockObjectFactory().getMockRequest().setServletPath(getTestPath("basic")); + doGet(); + assertFalse(((HttpServletResponse)getFilteredResponse()).containsHeader("X-LookMaNoJava")); + clearOutput(); + MockFilterConfig cfg = getWebMockObjectFactory().getMockFilterConfig(); + cfg.setInitParameter(PyFilter.FILTER_PATH_PARAM, getTestPath("filter")); + cfg.setInitParameter("header", "X-LookMaNoJava"); + // Set that PyServlet initialization has run as mockrunner's context doesn't indicate that + // it happened. + getWebMockObjectFactory().getMockServletContext().setAttribute(PyServlet.INIT_ATTR, "true"); + createFilter(PyFilter.class); + doGet(); + HttpServletResponse resp = (HttpServletResponse)getFilteredResponse(); + assertTrue(resp.containsHeader("X-LookMaNoJava")); + } +} Modified: trunk/jython/tests/modjy/java/org/python/util/PyServletTest.java =================================================================== --- trunk/jython/tests/modjy/java/org/python/util/PyServletTest.java 2009-06-22 06:48:53 UTC (rev 6491) +++ trunk/jython/tests/modjy/java/org/python/util/PyServletTest.java 2009-06-22 08:27:10 UTC (rev 6492) @@ -47,7 +47,7 @@ createServlet(PyServlet.class); } - private String doGetAndRead(String testName) { + protected String doGetAndRead(String testName) { getWebMockObjectFactory().getMockRequest().setServletPath(getTestPath(testName)); doGet(); String result = getOutput(); @@ -55,7 +55,7 @@ return result; } - private String getTestPath(String testName) { + protected String getTestPath(String testName) { return "/test/pyservlet/" + testName + ".py"; } @@ -84,5 +84,5 @@ }; } - private String basePath = System.getProperty("JYTHON_HOME") + "/Lib"; + protected String basePath = System.getProperty("JYTHON_HOME") + "/Lib"; } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |