paulb@215 | 1 | package uk.org.boddie.webstack.util; |
paulb@215 | 2 | |
paulb@215 | 3 | import java.io.*; |
paulb@215 | 4 | import java.util.*; |
paulb@215 | 5 | import javax.servlet.*; |
paulb@215 | 6 | import javax.servlet.http.*; |
paulb@215 | 7 | import org.python.core.*; |
paulb@215 | 8 | import org.python.util.PythonInterpreter; |
paulb@215 | 9 | |
paulb@215 | 10 | /** |
paulb@215 | 11 | * This servlet is used to re-serve JPython servlets. It stores |
paulb@215 | 12 | * bytecode for JPython servlets and re-uses it if the underlying .py |
paulb@215 | 13 | * file has not changed. |
paulb@215 | 14 | * <p> |
paulb@215 | 15 | * Many people have been involved with this class: |
paulb@215 | 16 | * <ul> |
paulb@215 | 17 | * <li>Chris Gokey |
paulb@215 | 18 | * <li>David Syer |
paulb@215 | 19 | * <li>Finn Bock |
paulb@215 | 20 | * </ul> |
paulb@215 | 21 | * If somebody is missing from this list, let us know. |
paulb@215 | 22 | * <p> |
paulb@215 | 23 | * |
paulb@215 | 24 | * e.g. http://localhost:8080/test/hello.py |
paulb@215 | 25 | * <pre> |
paulb@215 | 26 | * |
paulb@215 | 27 | * from javax.servlet.http import HttpServlet |
paulb@215 | 28 | * class hello(HttpServlet): |
paulb@215 | 29 | * def doGet(self, req, res): |
paulb@215 | 30 | * res.setContentType("text/html"); |
paulb@215 | 31 | * out = res.getOutputStream() |
paulb@215 | 32 | * print >>out, "<html>" |
paulb@215 | 33 | * print >>out, "<head><title>Hello World, How are we?</title></head>" |
paulb@215 | 34 | * print >>out, "<body>Hello World, how are we?" |
paulb@215 | 35 | * print >>out, "</body>" |
paulb@215 | 36 | * print >>out, "</html>" |
paulb@215 | 37 | * out.close() |
paulb@215 | 38 | * return |
paulb@215 | 39 | * </pre> |
paulb@215 | 40 | * |
paulb@215 | 41 | * in web.xml for the PyServlet context: |
paulb@215 | 42 | * <pre> |
paulb@215 | 43 | * <web-app> |
paulb@215 | 44 | * <servlet> |
paulb@215 | 45 | * <servlet-name>PyServlet</servlet-name> |
paulb@215 | 46 | * <servlet-class>uk.org.boddie.webstack.util.PyServlet</servlet-class> |
paulb@215 | 47 | * <init-param> |
paulb@215 | 48 | * <param-name>python.home</param-name> |
paulb@215 | 49 | * <param-value>/usr/home/jython-2.1</param-value> |
paulb@215 | 50 | * </init-param> |
paulb@215 | 51 | * <init-param> |
paulb@215 | 52 | * <param-name>servlet.file</param-name> |
paulb@215 | 53 | * <param-value>hello.py</param-value> |
paulb@215 | 54 | * </init-param> |
paulb@215 | 55 | * </servlet> |
paulb@215 | 56 | * <servlet-mapping> |
paulb@215 | 57 | * <servlet-name>PyServlet</servlet-name> |
paulb@215 | 58 | * <url-pattern>/*</url-pattern> |
paulb@215 | 59 | * </servlet-mapping> |
paulb@215 | 60 | * </web-app> |
paulb@215 | 61 | * |
paulb@215 | 62 | * </pre> |
paulb@215 | 63 | */ |
paulb@215 | 64 | public class PyServlet extends HttpServlet { |
paulb@215 | 65 | private PythonInterpreter interp; |
paulb@215 | 66 | private Hashtable cache = new Hashtable(); |
paulb@215 | 67 | private String rootPath; |
paulb@215 | 68 | private String servletFile = null; |
paulb@215 | 69 | |
paulb@215 | 70 | public void init() { |
paulb@215 | 71 | rootPath = getServletContext().getRealPath("/"); |
paulb@215 | 72 | if (!rootPath.endsWith(File.separator)) |
paulb@215 | 73 | rootPath += File.separator; |
paulb@215 | 74 | |
paulb@215 | 75 | Properties props = new Properties(); |
paulb@215 | 76 | |
paulb@215 | 77 | // Context parameters |
paulb@215 | 78 | ServletContext context = getServletContext(); |
paulb@215 | 79 | Enumeration e = context.getInitParameterNames(); |
paulb@215 | 80 | while (e.hasMoreElements()) { |
paulb@215 | 81 | String name = (String) e.nextElement(); |
paulb@215 | 82 | props.put(name, context.getInitParameter(name)); |
paulb@215 | 83 | } |
paulb@215 | 84 | |
paulb@215 | 85 | // Config parameters |
paulb@215 | 86 | e = getInitParameterNames(); |
paulb@215 | 87 | while (e.hasMoreElements()) { |
paulb@215 | 88 | String name = (String) e.nextElement(); |
paulb@215 | 89 | props.put(name, getInitParameter(name)); |
paulb@215 | 90 | } |
paulb@215 | 91 | |
paulb@215 | 92 | if (props.getProperty("python.home") == null && |
paulb@215 | 93 | System.getProperty("python.home") == null) { |
paulb@215 | 94 | props.put("python.home", rootPath + "WEB-INF" + |
paulb@215 | 95 | File.separator + "lib"); |
paulb@215 | 96 | } |
paulb@215 | 97 | |
paulb@215 | 98 | // Define the servlet file. |
paulb@215 | 99 | servletFile = props.getProperty("servlet.file"); |
paulb@215 | 100 | |
paulb@215 | 101 | PythonInterpreter.initialize(System.getProperties(), props, |
paulb@215 | 102 | new String[0]); |
paulb@215 | 103 | reset(); |
paulb@215 | 104 | |
paulb@215 | 105 | PySystemState sys = Py.getSystemState(); |
paulb@215 | 106 | sys.add_package("javax.servlet"); |
paulb@215 | 107 | sys.add_package("javax.servlet.http"); |
paulb@215 | 108 | sys.add_package("javax.servlet.jsp"); |
paulb@215 | 109 | sys.add_package("javax.servlet.jsp.tagext"); |
paulb@215 | 110 | |
paulb@215 | 111 | sys.add_classdir(rootPath + "WEB-INF" + |
paulb@215 | 112 | File.separator + "classes"); |
paulb@215 | 113 | |
paulb@215 | 114 | sys.add_extdir(rootPath + "WEB-INF" + File.separator + "lib", true); |
paulb@215 | 115 | } |
paulb@215 | 116 | |
paulb@215 | 117 | /** |
paulb@215 | 118 | * Implementation of the HttpServlet main method. |
paulb@215 | 119 | * @param req the request parameter. |
paulb@215 | 120 | * @param res the response parameter. |
paulb@215 | 121 | * @exception ServletException |
paulb@215 | 122 | * @exception IOException |
paulb@215 | 123 | */ |
paulb@215 | 124 | public void service(ServletRequest req, ServletResponse res) |
paulb@215 | 125 | throws ServletException, IOException |
paulb@215 | 126 | { |
paulb@215 | 127 | req.setAttribute("pyservlet", this); |
paulb@215 | 128 | |
paulb@215 | 129 | String spath = (String)req.getAttribute( |
paulb@215 | 130 | "javax.servlet.include.servlet_path"); |
paulb@215 | 131 | if (spath == null) { |
paulb@215 | 132 | spath = ((HttpServletRequest) req).getServletPath(); |
paulb@215 | 133 | if (spath == null || spath.length() == 0) { |
paulb@215 | 134 | // Servlet 2.1 puts the path of an extension-matched |
paulb@215 | 135 | // servlet in PathInfo. |
paulb@215 | 136 | spath = ((HttpServletRequest) req).getPathInfo(); |
paulb@215 | 137 | } |
paulb@215 | 138 | } |
paulb@215 | 139 | String rpath = getServletContext().getRealPath(spath); |
paulb@215 | 140 | |
paulb@215 | 141 | interp.set("__file__", rpath); |
paulb@215 | 142 | |
paulb@215 | 143 | // Instead of using the URL to find the Jython servlet, use a supplied |
paulb@215 | 144 | // property instead. |
paulb@215 | 145 | |
paulb@215 | 146 | if (servletFile == null) |
paulb@215 | 147 | throw new ServletException("No servlet.file specified in configuration."); |
paulb@215 | 148 | |
paulb@215 | 149 | HttpServlet servlet = getServlet(rootPath + servletFile); |
paulb@215 | 150 | if (servlet != null) |
paulb@215 | 151 | servlet.service(req, res); |
paulb@215 | 152 | else |
paulb@215 | 153 | throw new ServletException("No python servlet found for: " + rpath); |
paulb@215 | 154 | } |
paulb@215 | 155 | |
paulb@215 | 156 | public void reset() { |
paulb@215 | 157 | destroyCache(); |
paulb@215 | 158 | interp = new PythonInterpreter(null, new PySystemState()); |
paulb@215 | 159 | cache.clear(); |
paulb@215 | 160 | PySystemState sys = Py.getSystemState(); |
paulb@215 | 161 | sys.path.append(new PyString(rootPath)); |
paulb@215 | 162 | |
paulb@215 | 163 | String modulesDir = rootPath + "WEB-INF" + |
paulb@215 | 164 | File.separator + "jython"; |
paulb@215 | 165 | sys.path.append(new PyString(modulesDir)); |
paulb@215 | 166 | } |
paulb@215 | 167 | |
paulb@215 | 168 | private synchronized HttpServlet getServlet(String path) |
paulb@215 | 169 | throws ServletException, IOException |
paulb@215 | 170 | { |
paulb@215 | 171 | CacheEntry entry = (CacheEntry) cache.get(path); |
paulb@215 | 172 | if (entry == null) |
paulb@215 | 173 | return loadServlet(path); |
paulb@215 | 174 | File file = new File(path); |
paulb@215 | 175 | if (file.lastModified() > entry.date) |
paulb@215 | 176 | return loadServlet(path); |
paulb@215 | 177 | return entry.servlet; |
paulb@215 | 178 | } |
paulb@215 | 179 | |
paulb@215 | 180 | private HttpServlet loadServlet(String path) |
paulb@215 | 181 | throws ServletException, IOException |
paulb@215 | 182 | { |
paulb@215 | 183 | HttpServlet servlet = null; |
paulb@215 | 184 | File file = new File(path); |
paulb@215 | 185 | |
paulb@215 | 186 | // Extract servlet name from path (strip ".../" and ".py") |
paulb@215 | 187 | int start = path.lastIndexOf(File.separator); |
paulb@215 | 188 | if (start < 0) |
paulb@215 | 189 | start = 0; |
paulb@215 | 190 | else |
paulb@215 | 191 | start++; |
paulb@215 | 192 | int end = path.lastIndexOf('.'); |
paulb@215 | 193 | if ((end < 0) || (end <= start)) |
paulb@215 | 194 | end = path.length(); |
paulb@215 | 195 | String name = path.substring(start, end); |
paulb@215 | 196 | |
paulb@215 | 197 | try { |
paulb@215 | 198 | interp.execfile(path); |
paulb@215 | 199 | PyObject cls = interp.get(name); |
paulb@215 | 200 | if (cls == null) |
paulb@215 | 201 | throw new ServletException("No callable (class or function) "+ |
paulb@215 | 202 | "named " + name + " in " + path); |
paulb@215 | 203 | |
paulb@215 | 204 | PyObject pyServlet = cls.__call__(); |
paulb@215 | 205 | Object o = pyServlet.__tojava__(HttpServlet.class); |
paulb@215 | 206 | if (o == Py.NoConversion) |
paulb@215 | 207 | throw new ServletException("The value from " + name + |
paulb@215 | 208 | "must extend HttpServlet"); |
paulb@215 | 209 | servlet = (HttpServlet)o; |
paulb@215 | 210 | servlet.init(getServletConfig()); |
paulb@215 | 211 | |
paulb@215 | 212 | } catch (PyException e) { |
paulb@215 | 213 | throw new ServletException("Could not create "+ |
paulb@215 | 214 | "Jython servlet" + e.toString()); |
paulb@215 | 215 | } |
paulb@215 | 216 | CacheEntry entry = new CacheEntry(servlet, file.lastModified()); |
paulb@215 | 217 | cache.put(path, entry); |
paulb@215 | 218 | return servlet; |
paulb@215 | 219 | } |
paulb@215 | 220 | |
paulb@215 | 221 | public void destroy() { |
paulb@215 | 222 | destroyCache(); |
paulb@215 | 223 | } |
paulb@215 | 224 | |
paulb@215 | 225 | private void destroyCache() { |
paulb@215 | 226 | for (Enumeration e = cache.elements(); e.hasMoreElements(); ) { |
paulb@215 | 227 | CacheEntry entry = (CacheEntry) e.nextElement(); |
paulb@215 | 228 | entry.servlet.destroy(); |
paulb@215 | 229 | } |
paulb@215 | 230 | } |
paulb@215 | 231 | |
paulb@215 | 232 | } |
paulb@215 | 233 | |
paulb@215 | 234 | class CacheEntry { |
paulb@215 | 235 | public long date; |
paulb@215 | 236 | public HttpServlet servlet; |
paulb@215 | 237 | |
paulb@215 | 238 | CacheEntry(HttpServlet servlet, long date) { |
paulb@215 | 239 | this.servlet = servlet; |
paulb@215 | 240 | this.date = date; |
paulb@215 | 241 | } |
paulb@215 | 242 | } |