As we saw in Chapter 4, the Polls servlet presents a simple web API used to create a new instance of a poll, cast votes, and view the tally. Table 10.1 illustrates the Polls API.
Table 10-1. The Polls Servlet’s Web API
Action |
URL |
---|---|
Create a new poll |
/Polls?name=Picnic99&1=Sat+June+12&2=Sat+June+19&3=Sun+June+20 |
Cast a vote |
/Polls?name=Picnic99&vote=Sun+June+20 |
View the tally |
/Polls?name=Picnic99&tally= |
At the heart of Polls is a Java hash-of-hashes (HoH)—that is, a Java Hashtable whose keys are the names of poll instances (such as Picnic99) and whose values are hashtables. The keys of each interior hashtable are the names of the choices in that poll (e.g., “Sat June 19”), and the values count the votes for each of these choices. Table 10.2 depicts these structures.
In Perl, as in Java, it’s easy to create this kind of HoH. But a Perl CGI script can’t so easily meet the following requirements:
Retain the object in memory across multiple invocations of the script.
Protect the object from concurrent use by multiple clients.
Retrieve the object from disk at start-up and keep the in-memory version synched with the on-disk version as updates occur.
Larry Wall likes to say that Perl aims to makes easy things easy and
hard things possible. These hard things, though, are easy in Java.
(But as we’ll see, Java can also make some easy things hard.)
When the servlet host starts up Polls, its
instance data (the HoH) persists across calls to the servlet. A
conventional Perl CGI script has to refresh its in-memory objects on
each invocation, by querying a database or accessing some other kind
of disk-based storage. An Apache mod_ perl
script, always resident in memory, nevertheless can’t safely
use shared Perl data structures, because there’s no simple way
to synchronize access to them from concurrent Apache child processes.
In Java, protecting the Polls
object from
multiple concurrent voters is as easy as adding the
synchronized
keyword to the declaration of the
servlet’s vote( )
method. Example 10.1 shows how that can work.
Example 10-1. The Polls Servlet’s vote( ) Method
private synchronized void vote ( Hashtable hParams, HttpServletResponse res ) throws IOException { Hashtable hPoll = (Hashtable) Polls.get ( getPollName(hParams) ); Integer voteTally = (Integer) hPoll.get ( getVoteName(hParams) ); int tally = voteTally.intValue(); tally++; hPoll.put ( getVoteName(hParams), new Integer (tally) ); PrintStream out = new PrintStream(res.getOutputStream()); System.out.println ( "Vote received. Poll: " + getPollName(hParams) + "Vote: " + getVoteName(hParams) ); }
Each time you issue a request to a servlet, its host calls the
servlet’s service( )
method, passing two
arguments. The first, an HttpServletRequest
object, contains the CGI-style parameters passed by way of an HTTP
GET request, plus the HTTP headers included with
the request. The second, an HttpServletResponse
object, is used by the servlet to modify the HTTP headers sent back
to the client. When Polls’
service( )
method sees the parameter vote=Sun+June+20, it calls the
vote( )
method to increment the counter for that
item. What if two requests come in at the same time? The servlet runs
on a thread, and that thread blocks (has to wait) while executing a
method guarded by the synchronized keyword. This
technique can very easily coordinate requests to update a shared data
structure.
Saving and restoring the object are trivial tasks, too, thanks to
Java serialization. Java’s Hashtable
object implements the Serializable interface.
That means you can simply open a
FileOutputStream, hook an
ObjectOutputStream to it, and call
writeObject( )
to save it to disk, as shown in
Example 10.2.
Example 10-2. Serializing the Polls Object to a File
private synchronized void saveObjects() { try { FileOutputStream f = new FileOutputStream("polls.obj"); ObjectOutputStream o = new ObjectOutputStream(f); o.writeObject(Polls); o.flush(); o.close(); } catch (IOException e) { System.out.println(e); } }
The service( )
method calls
saveObjects( )
right after it calls
vote( )
. It does this only to make a permanent
record of the Polls object, so the servlet can
reconstitute it after a restart. Once it loads the object file at
start-up, though, it never refers to it. Votes are written to, and
tallies read from, the servlet’s own live in-memory
Polls object. The servlet is literally just a web
API to that object.
Loading the Polls object at start-up is equally simple, as shown in Example 10.3.
Example 10-3. Reading the Polls Object at Start-up
private void loadObjects() { try { FileInputStream f = new FileInputStream("polls.obj"); ObjectInputStream o = new ObjectInputStream(f); Polls = (Hashtable) o.readObject(); o.close(); } catch (IOException e) { System.out.println(e); } catch (ClassNotFoundException e) { System.out.println(e); } }
After loadObjects( )
runs, the
Polls object that was written out using
saveObjects( )
is reconstituted as a live Java
Hashtable.
This is nifty stuff. But even as it makes some hard things easy, Java
makes some easy things hard. For example, the vote(
)
method goes to a lot of trouble to bridge the chasm that
divides primitive
Java types (i.e., int) from their object
counterparts (i.e., Integer). The keys and
values of a Java Hashtable have to be objects,
not primitive types. But you can’t increment an
Integer, so the vote( )
method has to unpack the Integer, increment its
corresponding int, and then repackage it as an
Integer to store it back in the
Hashtable, as shown in Example 10.1. In Perl, these gymnastics would reduce to a
simple statement like:
$hash{$key}++;
Then there’s the problem of sorting the results of each poll. In Perl, you can sort the keys of a hashtable by their corresponding values like this:
@sorted_keys = sort { $hash{$b} <=> $hash{$a} } keys %hash;
For a Polls hashtable this yields, in
@sorted_keys
, a list of the choices ordered by the
number of votes for each choice. You can then print a tally, from
most popular choice to least, like this:
foreach $choice (@sorted_keys) { print "$choice: $hash{$choice} "; }
These kinds of quick, powerful data wrangling idioms aren’t built into the basic Java toolkit. There are plenty of freely available libraries that you can use to sort and rearrange Java objects, but even something as basic as an ordered collection isn’t part of the core language. The lesson is that Java giveth but also taketh away. Servlets are a great way to paste a web API onto dynamic, thread-safe, persistent, object-database compatible data structures. But servlets have to work harder to manipulate those live structures than do Perl scripts.