ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

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.

The List data screen
Figure 1. The list data screen

The Edit screen
Figure 2. The edit screen

Back to the List 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

Jason R. Briggs is an architect/developer working in the United Kingdom.


Return to ONJava.com.