Document last updated January 23, 2001. We are very interested in feedback that would make these materials better. Feel free to write the author, Jonathan Revusky.

Dissecting the mini-rolodex example

This example assumes that you have successfully built and run the example servlet, as described in the instructions here. Once you've successfully gone through those mechanical steps, you will probably want some help in dissecting the elements of this example.

The mini-rolodex example consists of three basic elements:

  1. The java code in our ServletInteraction subclass, MiniRoloServletInteraction.java
  2. The page templates, entries.html and edit.html
  3. The data definition files, recorddefs.xml and datasources.xml

Of the three elements above, it is the last two that are entirely new in this example. Note that our configuration file, web.xml needed to mention the two XML files as initialization parameters to the servlet. In essence, this is how a Niggle servlet gets its metadata which tells it what it needs to know about the persistence layer.

Let's get our bearings by looking at the java code and seeing how it relates to the supporting files. Just as in our first Hello, World example, the MiniRoloServletInteraction code contains an execDefault() method. This basically represents the action that the servlet should do if there is no "action=" parameter in the servlet request that tells it otherwise. As you can see, our default action is to display the entries, all the entries in the system. (A more useful rolodex would have a means of searching and filtering, but this is meant to be a minimal pedagogical example.)

Now, look at the displayEntries() method. Just as the first minimal example did, it assumes the presence of a page template, entries.html in this case, that contains the skeletal page to display. Now, there are some novel things. In the second line, we are assumed to be able to get an instance of MutableDataSource. The com.niggle.data.DataSouce interterface basically encapsulates the handle to an object that represents some source of data -- a collection of records, each of which is mapped (i.e. can be looked up) by its primary key.

In any case, as you can see directly below, the getData() method is simple enough. It obtains an instance of the default DataRegistry. And then it obtains the data source via lookup by name in the next line. Of course, this sort of begs the entire question of how there came to be a data source called "rolodex_data" that we could get our grubby hands on! The answer to this question lies in the datasources.xml file.

The DataSource Configuration File

Take a look at the datasources.xml file. This may be more or less intimidating to you, depending on the extent to which you have mucked with XML before this. There is not much reason to be intimidated, since really, XML is being used here to provide a human-readable external configuration format, in this case, so that we can keep the details of our persistence layer completely separate logically from the application-level code.

This is really the minimal example of such a file, since it defines exactly one DataSource, to which we assign the lookup-up name of "rolodex_data".

The CLASS attribute is set to that of the default flat file, which is actually defined to be: com.niggle.data.inmemory.InMemoryRecordSet. This is an implementation of the MutableDataSource interface that simply stores records in a flat text file. This is very useful for development. And actually, even for deployment, this data source implementation can be used for projects which have only reached a relatively small scale. At a later point, one could convert to using a DATASOURCE CLASS that is wraps the connection to an industrial-strength RDBMS. And you could achieve this basically by changing a line in your XML.

In any case, as the the nomenclature suggests, the records in an InMemoryRecordSet are kept in memory and persisted to a flat text file. The two properties defined, STORE and PERSIST_FREQUENCY, configure where that flat text file is located, and the frequency with which it is rewritten from scratch. If you configured a JDBC or JNDI data source here, you would likely have to set some other parameters, but we will leave those details for later. In any case, as you see, the details of your data persistence mechanism are completely transparent to your application-level java code as well as your presentation layer, the page templates.

Defining record metadata

Now, it's time to look at the other piece of the puzzle, the record metadata. If you look at recorddefs.xml, you will see that it defines exactly one record type, called "rolodex_entry". This file may be a bit harder to understand, since it leverages some defaults already defined in the Niggle framework. This is what the NIGGLE_BASE_RECS entity is about really. It is like an include, where certain canonical definitions occur. You can actually see the file being included, since it is in src/com/niggle/data/metadata/baserecs.xml.

In any case, the first field in our record definition is the field "unique_id", which, being the irst field that the system runs across, will be the record's primary key, since none is explicitly declared. Also, our DataSource class will automatically set it to a random unique key every time a record is stored.

The other fields defined in the rolodex_entry record are all strings. Some are required. Some are not. Also, certain of the fields have defined normalizations which provides some extra information to the niggle system about how to clean up the data when it is read in from the user interface. In any case, it will probably become apparent what a field being "required" or a field being "capitalized" means, simply by playing with the sample servlet.

Putting it all together

Okay, now let's turn our attention back to the java source code. Let's start by looking at the displayEntries(). As you can see, execDefault() simply invokes this method, so this is what the servlet does by default. First of all, it fishes out the page template it needs to display the entries. That is entries.html. Then it invokes the getData() method to get a handle to our one DataSource, which is "rolodex_data", as defined in our datasources.xml file. The next line invokes find() on the datasource with a null argument. The argument to find() is normally an instance of com.niggle.data.RecordFilter which defines a filtering mechanism so that a data source returns only a subset of its records. If you pass it a null, as we do here, you simply get back an instance of java.util.List containing all of them.

And the next line exposes all of those entries at one go!

So this is where some niggle magic occurs. Take a look at the entries.html. In there you will see that there is a directive containing <list entries as entry>. This starts off a freemarker iteration that runs over all of the records that we exposed in the list at the bottom of the displayEntries() method. Note how the template makes use of the various fields in the entry (defined in recorddefs.xml) to display dynamic text.

Now, if you have the general idea of the mechanisms involved, you may find that it is not so hard to get your arms around what the rest of the java code and page template code do. Take a look at the getEntry() method. Basically, the getEntry() method tries to get a record from our data source by its primary key, which is "unique_id". If it fails, it returns a completely new record, in an uninitialized state. Note how the execEdit() method fishes out the edit.html template, the one we use for editing a record -- that is, creating a new one or modifying an existing one.

The execProcess() method processes the results of the HTML form defined in edit.html. The entry.isInitialized() method tells us whether the entry returned by getEntry() is new or an already existing entry. If it is an already existing, initialized entry, we need to make a mutable copy, since Niggle data records are immutable once they are placed in a DataSource. This call to getMutableCopy() is unnecessary for the case in which the entry is new, since an uninitialized entry is already mutable. The call to DataUtil.fillRecordFromServletRequest goes through the parameters in the ServletRequest and fills in the fields of the record on that basis. To update an already existing entry in the DataSource, we invoke update() and in the other case, it is the insert() method. At the end of the execProcess() method, we invoke displayEntries(), which, as we saw, simply redisplays our default view, which is a list of all the rolodex entries we have.

Summing up

Though it is very minimal, this rolodex servlet actually has a surprising amount of functionality, given how little code it contains. Despite the small amount of code, this step presents some big hurdles of a conceptual nature. The step up from "Hello, World" to the mini-rolodex is really a pretty big leap, since, to fully understand this example, you have to get your arms around various distinct pieces that make up the whole.

I would encourage you to play with this app, changing things here and there, and trying to get a feel of how all the pieces work together. The step from -- at least conceptually. Now that you have been introduced to the major pieces, the next steps we will go through can be more incremental in nature.

Addendum: Using MySQL or another RDBMS instead of flat files

Note that the file datasources2.xml contains a substitute data source config file that uses an external RDBMS for persistence. To use this, you will almost certainly have to adjust some of the parameters. Also, for this to work, it is assumed that you actually have a database with the appropriate table and columns defined!

In any case, a good modus operandi will be to get your basic application logic working against the flat files, since it requires no external configuration and then switch to the external RDBMS afterwards if it proves necessary.