The REST of the Web
Pages: 1, 2
Adding Velocity to the Mix
Velocity is a templating engine I've written about before, and including a template engine with Jython makes a big difference in simplifying development.
To use Velocity with Jython, we'll need to add a few more .jar
files to Jetty's ext directory:
log4j,
Velocity
and
commons-collections.
Also, Velocity will not work out-of-the-box with Jython-generated
objects, so we need to customize the Velocity engine slightly.
JythonUberspect
is the customizer in question, and should be compiled into a .jar
and also added to $JETTY_HOME/ext as well. To make your life
easier, I've created the jyvel.jar file for you (which
just contains JythonUberspect.java and the class files it produces
when compiled) and can be found with the sample code. See
Resources.
Once JythonUberspect has been included, we can modify
enhancedservlet as follows, adding an init method:
from javax.servlet.http import HttpServlet
from org.apache.velocity.app import VelocityEngine
from org.apache.velocity.tools.\
generic.introspection import JythonUberspect
class enhancedservlet(HttpServlet):
ve = None
def init(self, config=None):
if config:
HttpServlet.init(self, config)
else:
HttpServlet.init(self)
if enhancedservlet.ve is None:
ve = VelocityEngine()
ve.setProperty(VelocityEngine.\
FILE_RESOURCE_LOADER_PATH,
self.getServletConfig().\
getServletContext().getRealPath('/') \
+ '/WEB-INF/templates')
ve.setProperty(VelocityEngine.\
FILE_RESOURCE_LOADER_CACHE, 'true')
ve.setProperty('input.encoding', 'UTF-8')
ve.setProperty(VelocityEngine.\
RUNTIME_LOG_LOGSYSTEM_CLASS,
'org.apache.velocity.runtime.log.\
SimpleLog4JLogSystem')
ve.setProperty(\
'runtime.log.logsystem.log4j.category',
VelocityEngine.getName())
ve.setProperty(VelocityEngine.\
UBERSPECT_CLASSNAME,
JythonUberspect.getName())
ve.init()
enhancedservlet.ve = ve
One point to note in this excerpt is that ve is a
variable of the class, and therefore similar to a Java
static. So rather than creating a new Velocity engine per servlet,
we'll have one global engine. Templates are served from a templates
directory in WEB-INF (i.e.,
${YOUR_WEBAPP_DIR}/WEB-INF/templates). Keeping them
in the WEB-INF directory keeps nosy surfers out of your
template code. The completed enhancedservlet can be
found in the sample code.
RESTified Servlets
Now I want to create a (reasonably) standards-compliant servlet, using the correct HTTP methods for my operations. I'll start with a simple screen to display a data list, and then a screen to display a single record in editable form.
list.vm (see Resources) is the template for displaying lists of my data:
<html>
<body>
<table border="1">
#foreach ( $row in $rows )
<tr>
#foreach ( $col in $row.cols )
<td>
#if ( $col == $row.key )
<a href="test5.py?key=${col}">
${col}</a>
#else${col}#end</td>
#end
</tr>
#end
</table>
<a href="test5.py?key=None">Add</a>
</body>
</html>
You'll notice that I have a $rows object (which is a
list), and each $row object has attributes for columns and
its key. If the column matches the key, then I create a link for
editing that row using the column value.
edit.vm is the template for editing a row of data:
<html>
<head>
<body>
<form action="test5.py" method="POST">
<table border="1">
#foreach ( $col in $obj.cols )
<tr>
<td>Column #${velocityCount}</td>
<td>
<input name="col${velocityCount}"
type="text"
#if ( $col == $obj.key )
disabled="disabled"
#end value="${col}" /></td>
</tr>
#end
</table>
<br />
<input type="hidden" name="key"
value="${obj.key}" />
<input type="hidden" name="method"
value="${method}" />
<input type="submit" value="Save" />
</form>
#if ( $method == 'POST' )
<form action="test5.py" method="GET">
<input type="hidden" name="key"
value="${obj.key}" />
<input type="hidden" name="method"
value="DELETE" />
<input type="submit" value="Delete" />
</form>
#end
<form action="test5.py" method="GET">
<input type="submit" value="Cancel" />
</form>
</body>
</html>
This displays the data columns, disabling the key value so it
can't be edited. While I could use JavaScript to modify a form
parameter for overriding the method, in this case I use multiple
forms. The first gets a method parameter, which is passed from the
servlet depending upon whether this is an existing record
(therefore an UPDATE or doPost) or a new
record (an INSERT or doPut). The second
form--only displayed if this is an existing record, and
therefore with the method set to POST--has the method override
parameter set to DELETE, while the last is just a call
back to the servlet with no parameters so we can display the list
again.
The servlet (with the file name test5.py) looks like this, so far:
from javax.servlet.http import HttpServlet
from org.apache.velocity import VelocityContext
from utils import *
# a simple data object with a key and some columns
class dataobj:
def __init__(self, key, cols):
self.key = key
self.cols = cols
global sequence
sequence = 1
# a map containing data objects
mydata = { }
# prepopulate with a few rows
for sequence in xrange(1, 4):
mydata['row%s_key' % sequence] =
dataobj('row%s_key' % sequence,
[ 'row%s_key' % sequence,
'row%s_col2' % sequence,
'row%s_col3' % sequence ])
class test5(enhancedservlet):
def doGet(self, request, response):
ctx = VelocityContext()
key = request.getParameter('key')
if key:
t = enhancedservlet.ve.\
getTemplate('edit.vm')
if key != 'None':
ctx.put('obj', mydata[key])
ctx.put('method','POST')
else:
ctx.put('obj', dataobj('None',
[ 'None', '', '' ]))
ctx.put('method','PUT')
else:
t = enhancedservlet.ve.\
getTemplate('list.vm')
ctx.put('rows', mydata.values())
response.setContentType(\
'text/html;charset=UTF-8')
pw = response.getWriter()
t.merge(ctx, pw)
pw.close()
From the code above you can see that if the parameter key is
present we use the template edit.vm; otherwise, we use
list.vm. The Velocity context is populated with data
depending upon whether key is an actual key value or set to None.
In the case of None, we construct a blank data object and put that
in the context. This much allows us to display a list of rows, and
display a single row for editing.
Next I'll implement a doDelete method:
def doDelete(self, request, response):
key = request.getParameter('key')
if not key or key == '':
response.sendError(\
response.SC_BAD_REQUEST,
'no key specified')
return
if not mydata.has_key(key):
response.sendError(response.SC_NOT_FOUND,
'%s was not found' % key)
return
del mydata[key]
wrappedrequest = modifiablerequest(request)
wrappedrequest.delParameter('key')
self.doGet(wrappedrequest, response)
This method performs some simple validation (checking for the
presence of a key parameter, and checking for the existence of that
key), before deleting the data from the
map. modifiablerequest (also found in
utils.py) is a subclass of
HttpServletRequestWrapper, and provides set and delete
parameter methods, so a request may be modified and then pushed on
to another method for processing. (In the above example, we perform
the delete, then pass the request on to doGet after
deleting the parameter key, so that doGet
displays the main data list.)
The doPut method again performs some simple
validation (also checking for the existence of a key
parameter), before creating a data object and then adding it to the
map. Again, processing is passed on to doGet:
def doPut(self, request, response):
key = request.getParameter('key')
if key and key != 'None':
response.sendError(\
response.SC_BAD_REQUEST,
'there is an existing key')
return
# generate a new key
global sequence
sequence = sequence + 1
key = 'row%s_key' % sequence
# create the data object
data = dataobj( key, [ key,
request.getParameter('col2'),
request.getParameter('col3') ])
mydata[key] = data
wrappedrequest = modifiablerequest(request)
wrappedrequest.delParameter('key')
self.doGet(wrappedrequest, response)
Finally, the doPost method checks for the existence
of a key, and updates the requisite data object, before calling
doGet:
def doPost(self, request, response):
key = request.getParameter('key')
if not key or key == 'None' or \
not mydata.has_key(key):
response.sendError(\
response.SC_BAD_REQUEST,
'invalid or missing key')
return
data = mydata[key]
data.cols[1] = request.getParameter('col2')
data.cols[2] = request.getParameter('col3')
wrappedrequest = modifiablerequest(request)
wrappedrequest.delParameter('key')
self.doGet(wrappedrequest, response)
Figures 1 to 3 show the final result when running the Jython
servlet under Jetty. Figure 1 shows the initial list of data rows
provided by the doGet, Figure 2 shows the edit screen when adding a
record, and Figure 3 shows the list screen again after the row has
been added.

Figure 1. The list data screen

Figure 2. The edit screen

Figure 3. Back to the list screen with a new row
Conclusion
This is a simple example, but hopefully gives you some idea of
the flexibility of this approach. There is a clean delineation of
operations and responsibilities within the servlet: Velocity gives
you a nice separation of view and, combined with Jython, gives the
flexibility of rapid web development. The amount of code you are
required to write for a typical web app, particularly traditional
GET/POST servlets handling a variety of different operations, can
be prodigious, so sticking to the HTTP protocol does force you to
cut to the essentials. But certainly, in my experience, simplifying
your code to basic HTTP operations does not necessarily mean losing
functionality for your end users, and it can help focus your
efforts on the important fundamentals.
Resources
- Sample code for this article
- Better, Faster, Lighter Java
- The Pragmatic Programmer
- Jython Essentials
Jason R. Briggs is an architect/developer working in the United Kingdom.
Return to ONJava.com.