Return to the generated Javadoc for this class.

Return to the generated Javadoc for this package.

Subclassing the ServletInteraction class

The class org.niggle.servlet.ServletInteraction is the most basic subclassing point in the Niggle framework. When you create a Niggle-based web application, you will necessarily create your own ServletInteraction subclass.

There are 3 basic things you will do in this subclass:

  1. Override the superclass constructor. This is necessary but trivial; it always is the same bit of boilerplate code.
  2. Create your execXXX methods that are the entry points into your application's functionality. (This is where the real work is, of course.)
  3. Override basic hooks that the framework uses to determine things: methods such as deduceLocale() or deduceSessionInfo() or hasValidLoginInfo().

The first step is trivial. It is always the same piece of boilerplate code:


                   public MyServletInteraction(HttpServletRequest request, 
                                              HttpServletResponse response, 
                                              NiggleConfig config) throws IOException {
                        super(request, response, config);
                   }
      

Of the three steps listed above, it is the last one that is optional, though if you want to use the built-in session-tracking mechanism, you will almost certainly have to override hasValidLoginInfo(), since this is the method the framework uses to determine whether it should create a session for a user -- or politely tell him to get lost! Typically, your implementation of this method will check if there is user/password information embedded in the servlet request (via a parameter or maybe a cookie) and check that against an access list in a file or database table somewhere. Aside from that, the base ServletInteraction class has usable out-of-the-box defaults. What is likely is that at the very end of your development process, you will want to refine these defaults to add that extra bit of polish to your app. So, a good basic rule of thumb is that the second step above, defining your execXXX methods, will occur at an early stage of application development, and the last step mostly towards the end, in a polishing-up stage.

Creating Your execXXX Application Entry Points

Methods that you create with the execXXX naming pattern are considered to be the basic functional entry points into your application. Here is the basic idea:

Your app deduces the action that corresponds to this servlet request. By default, a Niggle app will deduce the action by looking for an action=foo parameter in the servlet request. (This scheme can be configured by overriding the deduceAction() method, for example, but there is not any very strong reason to do so.) In other words, a servlet request with an embedded "action=foo" parameter is taken to be a call to the execFoo() method.

Let's consider the basic scenario of a user opening a URL like this:


                   http://www.myweb.com/myNiggleServlet?action=showOrder&order_id=1234

Well, the action=showOrder parameter is taken to mean that you want to invoke the method execShowOrder() on the ServletInteraction object. Your method might look something like this:


                   public void execShowOrder() throws IOException {
                       if (!checkSessionInfo())
                           return;
                       Integer order_id = null;
                       try {
                           order_id = new Integer(getParameter("order_id"));
                       }
                       catch (NumberFormatException nfe) {
                           throw new MissingRecordException("no such order, expecting an integer");
                       }
                       DataSource mydata = getDataSource("mydata");
                       Record order = mydata.get("order", order_id);
                       if (order == null) {
                           throw new MissingRecordException("no order with id# " + order_id);
                       }
                       page = getPage("showOrder.nhtml");
                       page.expose("order", order);
                   }

The above code could easily be a fragment of a hypothetical web application built on top of Niggle. The first line calls checkSessionInfo() since we are assuming that only authorized people can view this information. Of course, in some cases we are not concerned with this. What is also typical in a web app is that some "actions" are restricted to authorized users, while others are not. For instance, very typically, one can browse the messages on a message board, but if you want to post, you must be logged in.

In any case, if the call to checkSessionInfo() returns false, the method can just return, since the request has already been handled at a lower level. Assuming we go from here, we first try to deduce what order the person wants to see. This will typically be embedded in the servlet request. We are assuming in the above that there is an order_id=nnnn there that is an integer lookup key. We get that from the request. In the remainder of the method, we:

  1. Get a handle to the data source where the data lives. This was presumably configured in your datasources.xml config file.
  2. Get the data record. (If there is no record with that id, we throw an exception.)
  3. We get the appropriate page template object to display this information.
  4. We expose the record on the page as a template variable.

As simple as that. As I never tire of saying, the Niggle framework allows you to do a lot while writing very little Java code. Note how the guts of the framework handle issues such as:

What this gives you is that, freed of most of the typical repetitive guck, your execXXX handlers end up being extremely brief and readable, allowing a lot of clarity about what the code is actually doing!

Refining the Default Behaviors: What to Override

The ServletInteraction class provides a variety of hooks that you can override to refine the behavior of your app. The ones you are most likely to override are the ones that deal with user/session/login tracking. At the very least, you will almost certainly want to override hasValidLoginInfo() so that it hooks up to the relevant data. You would likely write a method something like:


                   protected boolean hasValidLoginInfo() throws IOException {
                      if (!hasParameter("user_id") || !hasParameter(password))
                          return false;
                      String userID = getParameter("user_id");
                      String password = getParameter("password");
                      DataSource userData = getDataSource("users");
                      Record user = userData.get("user", userID);
                      return (user != null) && password.equals(user.get("password"));
                   }

The above realistic-looking method looks for a user_id/password combination in the servlet request. This would likely be the result of the user filling in a login form if she was not already logged in. The code in question gets a data source called "users" and then tries to fish out the record corresponding to the user_id parameter. It then checks whether there is such a user and whether the password in the record corresponds to that user.

Once you override the hasValidLoginInfo() hook, then the other pieces do their thing by default. It's worth eyeballing the checkSessionInfo() method and understanding its simple-minded logic:


                   protected boolean checkSessionInfo() throws IOException {
                       if (this.hasSession())
                           return true;
                       else if (hasValidLoginInfo()) {
                           return createNewSession();
                       }
                       else {
                           unauthorized();
                           return false;
                       }
                   }

The above method, checkSessionInfo() is meant to be the method you will typically call at the top of an execXXX method if you want the "action" that this method represents to be restricted to users who have been authorized. It's quite simple:

  1. It calls hasSession() to see whether the user currently has a valid session. If so, it gives the green light by returning true.
  2. Now, if there is no current session, the plot thickens and the code invokes hasValidLoginInfo() to see whether it is allowed to create a new session. The default implementation of this method always returns true, and also sets the userID to "anon". For login/session functionality to work in an appealing way, you will almost certainly have to override the default implementation of hasValidLoginInfo().
  3. Now, finally, if the hasValidLoginInfo method gives a red light for creating a session, then we call the unauthorized() hook which, by default, simply sends the 401 (unauthorized) code back to the client.

The important thing to realize here is that all of the methods invoked in the above checkSessionInfo() code are hooks that you can override to put more polished behavior in this basic mechanism. As things stand, these methods are basically thin wrappers around default functionality in the servlet womb. For example, by default, the createNewSession() method will use the built-in session creation hook in the Servlet API. On the other hand, you might want to override this behavior to use some other kind of customized scheme for tracking sessions. No problem, though in that case, you would surely have to override hasSession() and deduceSessionInfo() to adapt their behavior to your custom scheme.

Also, the unauthorized() hook is something that you might well want to override. For example, rather than sending a 401 code, it might be nicer to present the user with a signup page or something like that.

Last Chance Cafe: the recover() method

The recover(IOException e) method provides a hook where you can catch general error conditions in a graceful manner. Note that the base implementation of this method simply rethrows the exception and then this bubbles up into the code in the servlet womb that handles it. In particular, if the exception passed in is an instance of org.oreodata.DataException it is very likely that you can recover fairly gracefully from the condition. For example, if a user failed to fill in a required field, you can simply redisplay the record. Here is an example:


                   protected boolean recover(IOException e) throws IOException {
                       try {
                           throw e;
                       }
                       catch (DataException e) {
                           if (action.equals("processOrder")) {
                               page = getPage("orderform.nhtml");
                               page.expose("error", e.getLocalizedMessage());
                               page.expose("order", e.getRecord()); 
                           }
                           else 
                              throw e;
                       }
                   }

In the above example, we use the recover() hook to allow us to recover gracefully from a situation where a DataException was thrown as a result of trying to process an order form. Rather than letting the exception bubble up into the servlet engine, probably outputting a stacktrace to the client, we simply redisplay the order form with the error variable embedded and the associated order record. For an example of this mechanism at work, see the mini-rolodex tutorial example.

Other points you may want to override

A method that you will very often override is exposeDefaultVariables() which gives you a convenient point at which to expose variables regardless of which execXXX method was invoked.

The deduceLocale() is a wrapper around the request.getLocale() method in the Servlet API. It is very likely that you would want to override this in a localized web app if, for example, you have stored user preference information that says what a user's preferred locale is.

Though there is not much of a strong pragmatic reason to do it, you could override the deduceAction() method, which by default looks for an "action=XXX" parameter in the servlet request. Note that if you do that, you should also override getURL(action) to correspond to your mapping scheme.

Bypassing the Page Template Mechanism

At this point, you will likely know that "approved" way of sending output to the client when using Niggle is to set the page variable. Typically, in the course of an execXXX call, you will have a line something like:

                 page = getPage("mytemplate.nhtml");

This causes the framework to fish out the appropriate page template object and then you will set the dynamic part of the output via one or more calls to page.expose().

At times, though, you may want to bypass the page template mechanism. Often it is desirable to handle a request by redirecting to another URL using the response.sendRedirect() method in the java servlet API. One thing to know is that there is a kludge variable called hasRedirected that, if set, indicates to the Niggle code not to try to output a page via the page template mechanism, that the output has been handled in another manner.

                 response.sendRedirect("http://somewhere.com");
                 hasRedirected = true;

You should also set this variable if you want to output directly to the servlet's output stream with println calls.

                 public void execBadPattern() throws IOException {
                     PrintStream out = new PrintStream(response.getOutputStream());
                     out.println("");
                     out.println("I am a bad boy");
                     out.println("");
                     out.println("I should really use a page template instead of this nonsense!!!!"); 
                     out.println("");
                     out.close();
                     hasRedirected = true; // gives the hint to Niggle not to use Page variable.
                 }
 

I point out the above for the sake of completeness and in case you want to do that. On the other hand, it escapes me just why you would really want to write a method like the one above.


Document last updated 20 June 2001, by Jonathan Revusky. If you have any feedback regarding this document, feel free to write the author.

Return to the generated Javadoc for this class.
Return to the generated Javadoc for this package.