So you know how to make Java Servlets with Jython and find it a useful alternative for programming in pure Java. I agree. It's just that there comes a point in (webapp) prototype's lifecycle where the source code has become so littered with HTML snippets that you begin to wonder whether there's some way to make things cleaner. (This, by the way, is roughly how all those templating engines for Python got started.)
My purpose is to give you a viable alternative for the layout
part, namely using JSP ("Java Server Pages" or whatever-the-hell
they are really called) and JSTL in combination with
HttpServletRequest objects and Servlet
forwarding. I do not claim that this is a better way to do HTML
templating than, say, with some Python templating engine, but this is
one idiomatic way how these things are done with Java. It's also
pretty easy this way.
With this approach, HTML and logic is quite neatly separated, but the logic, in the Jython Servlet, is still dynamic. Change your servlet's code and the functionality is updated, on the fly.
Note that in addition to the stuff you need to do so that you can run Jython Servlets, you need to setup JSTL also.
Warning: you may find this tutorial pretty long-winded and boring. If you want to skip all the boring stuff, go straight to the final scripts and JSP files.
Our goal is to make a page that you can use to search for weblogs. (For a total of four different weblogs; not quite in the league of Google or Technorati, but we're getting there..) The design consists of one JSP page and one Jython Servlet. Let's begin with the JSP.
First, we need to "import" the JSTL tag library to the JSP page. Like this:
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
Then, we need the search form. At first iteration, it'll be just like any normal HTML form:
<form method="GET" action="search.py"> <input type="text" name="q"> <input type="submit"> </form>
So far, so good. Now we can go about implementing our servlet.
We make a script called, surprise surprise,
search.py. It has the usual imports
and declarations:
import java, javax, sys
class search(javax.servlet.http.HttpServlet):
def doGet(self, request, response):
pass
def doPost(self, request, response):
pass
To make it support both GETs and POSTs, we just delegate GETs to the POST method, like this:
def doGet(self, request, response):
self.doPost(request, response)
We'll start with the request forwarding, which I briefly mentioned in the introduction. See, Java Servlets can forward the request to, say, a JSP page, which can then format the HTML. This way the servlet makes the number crunching and the JSP page makes the layout. The servlet "communicates" to the JSP page by putting stuff in the HTTP request (or in the HTTP session or in the servlet container context, but that's another story). And since this is possible in Java, it's possible in Jython, too.
First, you get a request dispatcher from the
HttpServletRequest object given as the argument and
then you make it forward the request to the page you
instantiated the dispatcher to. Like this:
def doPost(self, request, response):
dispatcher = request.getRequestDispatcher("search.jsp")
dispatcher.forward(request, response)
Now, if you go to the search.jsp page with your
browser and submit the form, the location field in your browser
should indicate that you're on the search.py
page. Indeed you are, in a way, but the HTML comes from the
JSP. (That's what we want.)
The real magic comes from "putting stuff in the request
object". All you need to do in the servlet code is to call
request.setAttribute(key, value) and the stuff is
available to the JSP page.
The first functionality we implement with request forwarding has got nothing to do with searching, but is essential to any search page on the web, namely keeping the search term in the field after the submit. Simple stuff, but some people still seem to forget this.
We try to obtain the supplied search term in the servlet code (if there is no search term, we just put an empty string in it) and put its value to the request object:
def doPost(self, request, response):
searchterm = request.getParameter("q")
if not searchterm:
searchterm = ""
request.setAttribute("current_q", searchterm)
dispatcher = request.getRequestDispatcher("search.jsp")
dispatcher.forward(request, response)
Now, in the JSP side, we can print this value with the JSTL tag
out, like this:
<c:out value="${current_q}"/>
We can put this in the value attribute of the
input field q:
<input type="text" name="q" value="<c:out value="${current_q}"/>">
This way we don't need to retype the search term each time. (This is a fundamental UI feature of search forms.)
OK. So we have the search term already figured out in the
servlet. Next, we implement the search functionality, in a
method called get_results(self, searchterm). Our
database is a list of tuples that consist of the name of the
weblog and its URL. The code for the searching is mostly as
simple as it gets, but has couple of curiosities: it uses Java's
lists and hashmaps. We'll get to that later.
sites = [
('Python owns us', 'http://weblog.hotales.org/portal/python'),
('Daily Python URL', 'http://www.pythonware.com/daily/'),
('online.effbot.org', 'http://online.effbot.org/'),
('Python News', 'http://www.python.org/')]
def get_results(self, searchterm):
search = searchterm.upper()
results = java.util.ArrayList()
for name, url in search.sites:
if (name.upper().find(search) > -1 or
url.upper().find(search) > -1):
result = java.util.HashMap()
result.put("name", name)
result.put("url", url)
results.add(result)
return results
And we call this method from the doPost() method
and put the results in the request object:
def doPost(self, request, response):
...
if searchterm:
results = self.get_results(searchterm)
request.setAttribute("results", results)
...
That's it. Back on the JSP side, we need to show the results.
Results for '<c:out value="${current_q}"/>':
Then, we format the results to a HTML table with the help of
JSTL's forEach tag:
<table>
<c:forEach items="${results}" var="result">
<tr>
<td>
<a href="<c:out value="${result['url']}"/>">
<c:out value="${result['name']}"/>
</a>
</td>
</tr>
</c:forEach>
</table>
What it says is that we loop through the Java collection that is
in the attribute named results in the default
context (the request context) and put the elements of the
iteration to a variable called result. With each
iteration, we make a table row and a cell in which we print a
HTML link whose URL is in the result variable, in
its 'url' attribute and name in 'name' attribute,
respectively. The URL and name attributes correspond to the
HashMap's entries. A HashMap
entry with the key 'url' is accessible to a JSTL tag with the
syntax like the above, ${variable['url']}.
(The more usual way in Java code is to use the so called Java
Beans — objects with properties that can be accessed with
getters and setters — technique. With them, you address
the properties with JSTL syntax like this:
${result.name}. You can do that with
Jython servlets too, but it is not that convenient anymore,
since you need to compile the "beans" to Java classes yourself
(well, as far as I know, anyway). It's easy to do the compiling,
sure, but it's not so dynamic anymore.)
Now, if you type 'python' in the search box and submit the form, you should see results like this:
Results for 'python': Python owns us Daily Python URL Python News
That's it, then!
What we accomplished was a neat separation of the logic and the layout (not quite MVC, but enough for our purposes), but still a very dynamic way to program the logic. And most importantly, with Python!
So what's with these Java's ArrayLists and
HashMaps? JSTL accepts only Java's collections for
its attributes, so we need to use them instead of Python's own
lists and dictionaries. It's a bit awkward at times, but Java's
collections have most of the functionality of Python's basic
datatypes. And if we're not so concerned about the speed of the
servlet, we can convert back and forward from Java's collections
to Python's lists and dictionaries. Not very elegant, but works.
28.1.2005,
Jarno Virtanen. Python owns us, my weblog. Send feedback, corrections to jajvirta@gmail.com.