by John O'Hanley

Introduction to WEB4J: Web development for minimalists

feature
Oct 9, 200837 mins

Could a simpler Web application framework ease your pain?

As Java Web application frameworks have become more powerful and flexible, they’ve also become more complex. John O’Hanley’s WEB4J framework in many ways flies in the face of this trend: it offers few customization options, but is easy to learn and work with. Read on to learn about the unusual (and somewhat contrarian) development principles behind WEB4J. You’ll also have the opportunity to test out the framework by building a feature for a sample Web application.

For me, a good framework should be understandable by a goat — or at least by the lower orders of primates. I don’t like a framework that could effectively double as an IQ test. I like tools that make me say, “Oh, is that all there is? Is it really that simple?” I like them because I know my coding will sprint like Usain Bolt, not waddle along like an aging Orson Welles. The simpler the framework, the faster you can learn it, and the faster you can use it.

When it comes to the development process, most tools should be pain-killers — that is, they should address some sort of pain. At best, they should remove the pain entirely; if that’s not possible, they should minimize the pain. But my experience in building Java Web apps has been that the tools currently in wide use often cause more pain than they relieve. The types of pain I’ve suffered have been many and various:

  • Most Web applications are too complex — and complex tools are, by definition, hard to learn and hard to use. Many popular Web frameworks include hundreds or thousands of classes published in their javadocs; in contrast, WEB4J has only 85 published classes. Building plain, boring Java Web apps that just pump data into and out of a database should be just that — boring. But it’s often a long-distance phone call away from boring, because it’s so complicated. The fundamental thesis of WEB4J is that currently building Java web apps is unnecessarily complex. If you don’t accept this thesis, then WEB4J is not for you, and you should not waste another femtosecond reading this article.

  • Many tools make extensive use of XML files. But coding in XML is widely recognized as a particularly fruitful source of pain. XML files aren’t part of compiled code, so errors are often found only at runtime. XML syntax is widely regarded as verbose and clunky, and many modern IDEs are wonderfully rich tools for editing Java but not much help with XML files. Unfortunately, many frameworks place XML at the very core of their design.

  • Some tools force you to abandon things you already know how to do, and replace them with a parallel set of techniques specific to that tool. For instance, in Struts 1.x, you typically don’t implement form controls using standard HTML; instead, the framework forces you to replace standard form controls with a set of custom tags, one for each type of control. Tools should build upon what you already know, and not force you to learn a different way of doing essentially the same thing.

  • Many tools explicitly declare themselves to be “flexible” and “powerful” in their marketing blurbs. But flexibility necessarily implies a certain amount of added complexity. It simply takes more time and experience to understand all the options, and to make the right choices. If you are looking for a simpler approach, then all these decision points can be a source of pain.

  • The larger the set of tools used by a given app, the more difficult it becomes to train people to build and maintain that app. If, in addition, some of those tools have a long learning curve, then the problem is aggravated. Moving less experienced folk onto projects becomes increasingly difficult.

What’s different about WEB4J?

What’s different about WEB4J is its central design goal. Simply put, WEB4J’s central design goal is aggressive minimalism, a kind of minimalism not seen in other Java tools. This minimalism is manifest in two ways: the small size of its API, and in the concision of apps built using the framework.

Here’s a summary of what’s different about WEB4J:

  • It’s very small, and has a philosophy of deep simplicity and minimalism.
  • It’s not free of charge, it’s not open source, and it has nothing to do with rich Internet apps.
  • It enables package-by-feature. Each feature gets its own directory. That directory contains all items related to a single feature, and only that feature (code, JSP, and SQL statements, and the like).
  • It lets you put SQL in plain .sql text files. It does not use object-relational mapping.
  • It lets you implement forms with plain HTML.
  • it lets your model objects be immutable, instead of being JavaBeans.
  • It lets your model objects be responsible for their own validation.
  • It lets multilingual apps have almost exactly the same style as single-language apps.
  • It lets your apps assist in their own translation, by avoiding ResourceBundle and using the database instead.

Some other important things about WEB4J:

  • It’s a full stack, and helps you build all parts of your app.
  • It’s focused on the task of creating a plain vanilla HTML interface to a relational database.
  • It’s about request/response, not components and events.
  • It does not rely on JavaScript.
  • It has no custom annotations.
  • It has no custom XML files (only web.xml is used).
  • It uses convention over configuration in several important ways.
  • It requires a minimal toolset: JDK 1.5+, Servlets 2.4+, JSP 2.0+, JSTL 1.1+, and SQL.
  • It doesn’t depend directly on any third-party JARs.
  • It has good protection against common hacks such as cross-site scripting, cross-site request forgery, and SQL injection.

How does WEB4J work?

This article will show you how you can implement a typical feature using the WEB4J framework. It uses code taken from the WEB4J tutorial application, called Electricity Tracker. This is a minimal application that tracks spending on electricity. It doesn’t exercise all aspects of WEB4J, but it’s suitable for learning about the framework’s core features.

WEB4J vs. harmful idioms

My earlier JavaWorld article, “Four harmful Java idioms, and how to fix them,” discusses some of the common Java programming problems that I tried to address in the WEB4J framework. In particular, the article discusses the advantages to using package-by-feature, and to preferring immutable objects over JavaBeans.

The code that you’ll examine in this article does not encompass the full Electricity Tracker application; rather, it represents the implementation of a single, typical feature of the app. (In WEB4J, the term feature denotes a screen or page within an application.) Figure 1 is a screenshot of the Edit feature of Electricity Tracker, which will be the subject of analysis for the remainder of this article.

 

Figure 1. Electricity Tracker’s Edit screen (click to enlarge)

It’s very plain and simple, which is appropriate for a tutorial application. As you can see, the feature is a single page that includes a form and a listing. This single page implements five different operations:

  • It can list existing records.
  • It can add a new record, using the form.
  • It can fetch an existing record, and populate the form with it, in preparation for editing.
  • It can edit a record using the form, and POST the changes (this operation forms a pair with the preceding one).
  • It can delete a record.

This feature is implemented with three Java classes, a JSP, and an .sql file containing SQL statements:

  • Spending.java: The model object
  • SpendingDAO.java: The Data Access Object (DAO)
  • SpendingAction.java: The action
  • view.jsp: The user interface
  • statements.sql: The underlying SQL for each operation

These items all live in the same directory/package. That particular directory belongs to the feature. The directory includes all items related to the feature, and only those items — an example of package-by-feature, which is WEB4J’s recommended packaging style. Over the course of the rest of this article, we will examine each of the items in this directory in turn.

JSP and SQL

Your tour of the Edit feature will begin with with view.jsp and statements.sql. Once you understand how these work, you’ll be ready to examine the Java classes, to see how the view and the SQL operations are wired together. (The code is essentially an adaptor layer between the view and the SQL operations.) Listing 1 contains the full contents of view.jsp.

Listing 1. view.jsp

<%-- List-and-edit form for Electricity Spending. --%>
<%@ include file="/JspHeader.jsp" %>

<c:set value='SpendingAction' var='baseURL'/>
<tags:setFormTarget using='${baseURL}' />

<%-- Form for adds and edits. --%>
 <w:populate using="itemForEdit">
  <form action='${formTarget}' method="post" class="user-input">
   <input name="Id" type="hidden">
   <table class="legacyTable" align="center">
    <tr title='Format: MMDDYYYY' >
     <td><label>Date Paid *</label></td>
     <td><input name="DatePaid" type="text" maxlength='12'> MMDDYYYY</td>
    </tr>
    <tr title='Range 0..10,000. Includes taxes.'>
     <td><label>Amount *</label></td>
     <td><input name="Amount" type="text"  maxlength='10'></td>
    </tr>
    <tr title='Range 0..10,000'>
     <td><label>KWH *</label></td>
     <td><input name="KilowattHours" type="text"  maxlength='5'></td>
    </tr>
    <tr title='Sometimes the meter reading is estimated'>
     <td><label>Estimated Reading?</label></td>
     <td><input name="IsEstimated" type="checkbox" value='true'>Yes</td>
    </tr>
    <tr title='Type of building'>
     <td><label>BuildingType *</label></td>
     <td>
       <select name='Facility'>
         <option></option>
         <c:forEach items='${facilities}' var='item'>
          <option value='${item.id}'>${item}</option>
         </c:forEach>
       </select>
     </td>
    </tr>
    <tr title='Up to 100 characters'>
     <td><label>Comment</label></td>
     <td><input name="Comment" type="text" maxlength='100'></td>
    </tr>
 </w:populate>
    <tr>
     <td align="center" colspan=2>
      <input type=submit value="Add/Edit">
     </td>
    </tr>
   </table>
  </form>

<P>
<%-- Listing of all items. --%>
<table class="report legacyTable" title="Spending" align="center">
 <caption>Electricity Spending</caption>
 <tr>
  <th title="Line Number">#</th>
  <th title='Date Paid'>Date Paid</th>
  <th title='Amount Paid'>Amount</th>
  <th title='Kilowatt Hours'>KWH</th>
  <th title='Estimated Reading'>Est</th>
  <th title='Amount Per KWH'>Per KWH</th>
  <th>Building Type</th>
  <th>Comment</th>
 </tr>
<w:alternatingRow>
<c:forEach var="item" items="${itemsForListing}" varStatus="index">
 <tr class="row_highlight">
  <td title="Line Number">${index.count}</td>
  <td align='right'>
    <c:set value="${item.datePaid}" var="paidOn"/>
    <w:showDate name='paidOn' pattern='MMM dd yyyy'/>
  </td>
  <td align="right">
   <fmt:formatNumber value="${item.amount}" pattern='#,##0.00'/>
  </td>
  <td align='right'>
   <fmt:formatNumber value="${item.kilowattHours}" pattern='#,##0'/>
  </td>
  <td align='left'>
   <c:if test="${item.isEstimated}"><span title='Estimated Reading'>E</span></c:if>
  </td>
  <td align='right'>
   <fmt:formatNumber value="${item.amount/item.kilowattHours}" pattern='#,##0.00'/>
  </td>
  <td align='left'>
   ${item.facility}
  </td>
  <td align="left">${item.comment}</td>
  <tags:editLinks baseURL='${baseURL}' id='${item.id}'/>
 </tr>
</c:forEach>
</w:alternatingRow>
</table>

Going multilingual

If the sample application were multilingual, then the JSP in Listing 1 would need to change. All of the fixed text, such as the column names, form labels, and so on, would need to be dynamically translated at runtime. Although it’s not shown in the above example, WEB4J has taken pains to allow you to implement such translation in a way that is minimally invasive. WEB4J provides translation tags for JSPs, but they are often able to translate large blocks of markup all at once, as opposed to only translating individual snippets of text. In this way, the overall character of the markup is not significantly changed when moving from a single-language app to a multilingual app.

The most important aspect of the view is its familiarity. Its overall look is not very different from plain HTML. In particular, the form controls are implemented with ordinary HTML tags, and not with a custom set of form control tags. The various <c:...> and <fmt:...> tags will also be familiar to anyone who’s used the JSP Standard Tag Library (JSTL).

view.jsp does not render a complete HTML page; instead, it participates in a simple templating mechanism. view.jsp is actually passed to a template JSP, where it is taken as the body of a template. The idea is that the template JSP defines a common layout shared by a number of features.

The views in WEB4J differ from most other tools in several respects:

  • There are no custom tags for form controls
  • No custom widgets are supplied
  • The WEB4J app relies only on plain HTML and JSPs/JSTL
  • Components, events, and listeners are not used

There are two references to JSP tag files, shown in Listing 2.

Listing 2. Tag file references

<tags:setFormTarget using='${baseURL}' />
<tags:editLinks baseURL='${baseURL}' id='${item.id}'/>

These tag files are part of the JavaServer Pages spec, not part of the WEB4J toolkit. They simply encapsulate JSP snippets, to eliminate repeated boilerplate markup.

The <w:...> tags are part of the WEB4J toolkit. To begin to understand how they work, take a look at Listing 3, which shows how the application populates forms.

Listing 3. Populating forms

<w:populate using="itemForEdit">
  ..regular HTML form goes here..
</w:populate>

The <w:populate> tag contains a form (or part of one). Its job is to populate the form with data, which is usually taken from a model object. The using attribute refers to a key name for a particular model object, here named itemForEdit. That model object may have been placed in request scope by the SpendingAction class (which you’ll see later). Listing 4 shows the method in the action class that places itemForEdit into request scope.

Listing 4. Placing itemForEdit into request scope

@Override protected void attemptFetchForChange() throws DAOException {
  fLogger.fine("Attempting to fetch an existing Spending item.");
  Spending model = fDAO.fetch(getIdParam(ID));
  if( model == null ){
    addError("Item no longer exists. Likely deleted by another user.");
  }
  else {
    addToRequest(ITEM_FOR_EDIT, model);
  }
}

If itemForEdit is indeed present, then the <w:populate> tag will populate the form, taking data from itemForEdit, in preparation for editing. If it is not present, then the form is presented as is, with no prepopulated data. In this state, the form is ready for an add operation, instead of an edit.

There is a second potential source of data for the form. If the user POSTs data that doesn’t pass validations defined by the model object, then the form is redisplayed to the user, with the erroneous input indicated as such.

The remaining WEB4J tags in the JSP are of relatively minor importance, but for the sake of completeness you should get a closer look. The <w:alternatingRow> tag, shown in Listing 5, is used to change the appearance of listings.

Listing 5. Changing the appearance of listings

<w:alternatingRow>
 <c:forEach var="item" items="${itemsForListing}" varStatus="index">
  <tr class="row_highlight">
   ..a number of TD tags..
  </tr>
 </c:forEach>
</w:alternatingRow>

The idea behind this tag is simply that listings are more legible when the rows alternate in appearance. The <w:alternatingRow> tag will alternate the class attribute of each <TR> tag found within its body.

The <w:showDate> tag, shown in Listing 6, is, logically enough, for rendering dates.

Listing 6. Rendering dates

<w:showDate name='paidOn' pattern='MMM dd yyyy'/>

There are JSTL tags for the same operation; this tag was provided as an alternate. It will often be more convenient to use, as it can use a centrally defined date format and thus lets you define a standard date format for each application.

Now you’re ready to move on to the database. Listing 7 contains the statements.sql file, which contains all SQL operations related to this feature. Under the recommended style, a WEB4J app would use a different statements.sql file for each feature. That should in nearly all cases eliminate contention between developers for the same file.

Listing 7. statements.sql

LIST_SPENDING {
 SELECT Id, DatePaid, Amount, KWH,  IsEstimated, FacilityFK, Comment
 FROM Spending
 ORDER BY DatePaid DESC
}

FETCH_SPENDING {
 SELECT Id, DatePaid, Amount, KWH, IsEstimated, FacilityFK, Comment
 FROM Spending
 WHERE Id=?
}

ADD_SPENDING  {
 INSERT INTO Spending (DatePaid, Amount, KWH, IsEstimated, FacilityFK, Comment) VALUES (?,?,?,?,?,?)
}

CHANGE_SPENDING {
  UPDATE Spending SET DatePaid=?, Amount=?, KWH=?, IsEstimated=?, FacilityFK=?, Comment=? WHERE Id=?
}

DELETE_SPENDING {
  DELETE FROM Spending WHERE Id=?
}

This is an ordinary text file with an .sql extension. It contains a number of SQL statements, each of which is ready to be passed to a PreparedStatement. (Using PreparedStatement protects you from SQL injection attacks.) Each statement is placed in a block, the name of which forms an identifier for the SQL statement. That identifier is referenced in your code, and forms a bridge between SQL-land and Java-land. Listing 8 provides an example.

Listing 8. An SqlId referencing an SQL statement

public static final SqlId DELETE_SPENDING = new SqlId("DELETE_SPENDING");

The text passed here to the SqlId() constructor must match a block identifier in one of your .sql files. Everywhere else in your code, the underlying SQL statement can only be referenced using this SqlId object. Thus, use of the magic String ("DELETE_SPENDING", in this case) is limited to a single line.

Paranoid validation

Upon startup, WEB4J does some delightfully paranoid validation on these identifiers. It reads in two sets of identifiers, one from your .sql files, and another from the SqlId objects declared in code. Those sets must match exactly, or it’s game over. If there’s a typographical error, or if there are any orphaned items in either your .sql files or in your code, then your application will die a quiet but noble death upon startup, and will not run. Neither mismatches nor cruft are permitted. This protects you, because nasty career-limiting runtime errors are replaced with failures upon startup.

WEB4J can also attempt to precompile all of your SQL statements upon startup as well. The intent is mostly to confirm that your SQL is well-formed. Unfortunately, SQL precompilation is not supported by all databases, so this option is not always available.

SELECT ordering convention

The SELECT statements must follow an ordering convention. The order of columns in the ResultSet must match the order of arguments to a corresponding model object constructor. For example, you can compare the columns returned by the SELECTs in Listing 7 with the arguments of the Spending() constructor, shown in Listing 9.

Listing 9. Constructor arguments must match ResultSet column order

public Spending(
   Id aId, Date aDatePaid, BigDecimal aAmount, Integer aKiloWattHours,
   Boolean aIsEstimated, Id aFacility, SafeText aComment
) throws ModelCtorException {
   ...
}

Can you see that the SELECT columns match up one to one with the constructor arguments? It’s this simple ordering convention that allows your persistence classes (DAOs) to be so compact. No explicit mapping is required. It’s all implicit, simply in the order of items.

The WEB4J data layer as you’ve seen it so far differs from other tools in a number respects:

  • It does not use object-relational mapping.
  • It’s focused on ResultSets generated with plain SQL. In this regard, WEB4J is roughly similar to the iBATIS tool.
  • The database schema structure is not repeated anywhere in the application layer.
  • It uses ordering conventions, not naming conventions or direct mapping.

But the WEB4J data layer is not mandatory in WEB4J applications. You can implement persistence using any data layer you wish.

The model object

Now you’re ready to tackle the application’s model object, Spending.java. The complete code is in Listing 10.

Listing 10. Spending.java

package hirondelle.electricity.main.home;

import java.util.*;
import java.math.BigDecimal;
import hirondelle.web4j.model.ModelCtorException;
import hirondelle.web4j.model.Id;
import hirondelle.web4j.security.SafeText;
import hirondelle.web4j.model.Check;
import hirondelle.web4j.model.ModelUtil;
import hirondelle.web4j.model.Code;
import hirondelle.web4j.util.Util;
import hirondelle.electricity.codes.CodeTable;
import static hirondelle.web4j.util.Consts.FAILS;

public final class Spending {

  /**
  * Constructor.
  *
  * @param aId optional
  * @param aDatePaid required, year in range 2000..2100
  * @param aAmount required, in range 0..10000.00
  * @param aKiloWattHours required, range 1..10000
  * @param aIsEstimated optional, coerced to false if null
  * @param aFacility required, code for residential or commercial property
  * @param aComment optional, length 0..100 characters
  */
  public Spending(
   Id aId, Date aDatePaid, BigDecimal aAmount, Integer aKiloWattHours,
   Boolean aIsEstimated, Id aFacility, SafeText aComment
  ) throws ModelCtorException {
    fId = aId;
    fDatePaid = (aDatePaid == null ? null : aDatePaid.getTime());
    fAmount = aAmount;
    fKiloWattHours = aKiloWattHours;
    fIsEstimated = Util.nullMeansFalse(aIsEstimated);
    fFacility = CodeTable.codeFor(aFacility, CodeTable.FACILITY);
    fComment = aComment;
    validateState();
  }

  public Id getId() { return fId; }
  public Date getDatePaid() {  return new Date(fDatePaid); }
  public BigDecimal getAmount() { return fAmount;  }
  public Integer getKilowattHours() {  return fKiloWattHours; }
  public Boolean getIsEstimated() { return fIsEstimated; }
  public Code getFacility() { return fFacility; }
  public SafeText getComment() { return fComment;  }

  @Override public String toString(){
    return ModelUtil.toStringFor(this);
  }

  @Override public  boolean equals(Object aThat){
    Boolean result = ModelUtil.quickEquals(this, aThat);
    if ( result ==  null ) {
      Spending that = (Spending) aThat;
      result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
    }
    return result;
  }

  @Override public int hashCode(){
    return ModelUtil.hashCodeFor(getSignificantFields());
  }

  // PRIVATE //
  private final Id fId;
  private final Long fDatePaid;
  private final BigDecimal fAmount;
  private final Integer fKiloWattHours;
  private final Boolean fIsEstimated;
  private final Code fFacility;
  private final SafeText fComment;

  private static Long YEAR_2000;
  private static Long YEAR_2100;
  static {
    Calendar calendar = new GregorianCalendar();
    calendar.set(2000, 0, 0, 0, 0, 0); //0-based month, 0s for h:m:s
    YEAR_2000 = calendar.getTime().getTime();
    calendar.set(2100, 0, 0, 0, 0, 0);
    YEAR_2100 = calendar.getTime().getTime();
  }

  private void validateState() throws ModelCtorException {
    ModelCtorException ex = new ModelCtorException();

    if ( FAILS == Check.required(fDatePaid, Check.range(YEAR_2000, YEAR_2100))) {
      ex.add("Date paid must have year in range 2000..2100");
    }
    if ( FAILS == Check.required(fAmount, Check.range(new BigDecimal("0"), new BigDecimal("10000")))) {
      ex.add("Amount paid must be in range 0..10,000");
    }
    if ( FAILS == Check.required(fKiloWattHours, Check.range(0,10000))) {
      ex.add("Kilowatt-Hours must be in range 1..10,000");
    }
    if ( FAILS == Check.required(fIsEstimated) ) {
      ex.add("Estimated is required.");
    }
    if ( FAILS == Check.required(fFacility) ) {
      ex.add("Building Type is required.");
    }
    if ( FAILS == Check.optional(fComment, Check.range(0,100))) {
      ex.add("Comment cannot be longer than 1,000 characters.");
    }

    if ( ex.isNotEmpty() )  {
      throw ex;
    }
  }

  private Object[] getSignificantFields(){
    return new Object[] {fDatePaid, fAmount, fKiloWattHours, fIsEstimated, fFacility, fComment};
  }
}

As you can see, this model object differs from its counterparts in most Web application frameworks in several ways:

  • Objects of this class are immutable, as they can’t change their state (that is, their data) after construction.
  • This class is not a JavaBean.
  • The class performs its own validation. Surprisingly, many frameworks advocate performing such validation in other places.
  • The class’s equals() and hashCode() methods are explicitly tied to the same fields.
  • It uses various building block classes defined by the framework: SafeText, Id, and Code.

This particular class’s immutability was probably the first thing you noticed about it. Its state cannot change after the object is constructed. Many people vigorously denounce the idea of using immutables to represent editable data. To them, it doesn’t seem possible. But that’s exactly what WEB4J applications do. Immutable objects have many desirable characteristics.

The fundamental idea is simply that different objects are used to represent different data. Another important point is that objects typically live only for the duration of a single request. They are short-lived. When used like this, the state of a single object never needs to change after it’s created.

Of course, there are indeed cases in which a mutable model object may be appropriate. The point is that WEB4J allows you to prefer immutable objects, but use a mutable object whenever you need to.

To show how it’s possible to use immutable model objects, the next few sections will walk you through the various editing operations at a fairly high level. Keep the Spending class in Listing 10 in mind as you read on.

Creating a spending record

To create a new entry in the SPENDING table, the user enters data into a blank form, which is then POSTed. An attempt is made to create a Spending object. The user input is passed to the Spending constructor; the data is validated directly in the constructor, by calling the validateState() method. If one or more validations fail, then the constructor throws a ModelCtorException, and the associated error messages are displayed to the end user, along with the original input. If all validations pass, then the Spending constructor completes successfully. Its data is extracted via the various getXXX() methods, and stored in the database (via an INSERT operation). Here, there is clearly no need for the object to change state after construction.

Editing a spending record

Editing data is actually a two-step operation. First, an item to be edited is fetched from the database (in a SELECT operation). This time, ResultSet columns are passed to the Spending constructor, which performs the same validations that would occur during a creation operation. To complete this first step, the Spending object is then rendered in the user interface, in an HTML form populated with the model object’s data. Again, the various getXXX() methods are used to populate the form with values to be edited. Once the item to be edited is rendered in the form, it simply falls out of scope — it’s not stored in the session, for example.

Next comes the second step in the edit. The end user makes some changes, and then POSTs the form. Once again, a new Spending object is constructed. Again, it simply represents the data that the user just entered. This object has no knowledge of what the corresponding record in the database contains at that moment. Other than possible differences in the data, the only difference between this operation and the original creation and INSERT is that the Spending object now has a non-null ID, which it didn’t have during the initial add operation.

Again, the exact same validations are performed by the Spending() constructor, and any problems are reported in exactly the same way as before. If all validations pass, then the data carried by this new Spending object is applied to the database in an UPDATE operation that sets all columns to new values, without worrying about what has changed and what has not. The various getXXX() methods are used to supply the new values to the database.

So that’s it. At no point was it necessary to change the state of a Spending object after its construction. If the data is edited, then a new Spending object is created, and it’s used to simply overwrite the old database record in its entirety.

You might object that the creation of new objects is an expensive operation. But in truth it’s been a long time since object creation has been a great drag on Java performance in the great majority of cases. For instance, the stack traces generated by containers are always very extensive. A large number of stack layers necessarily implies the existence of a large number of objects being created for each request. Is one more object really going to matter? Can you even measure the difference? I suggest that, most of the time, it won’t, and you can’t.

toString(), equals(), and hashCode()

The toString(), equals(), and hashCode() methods should be implemented for every model object. WEB4J provides utility classes for implementing these methods. In Spending.java, note that the getSignificantFields() method is called by both equals() and hashCode(). This is because these two methods need to reference the same fields. This point is usually overlooked by other frameworks.

Constructors and validation

There is a single constructor that takes all fields. If one or more arguments are invalid, then the constructor throws a ModelCtorException. This forms the only real constraint on your model objets: the constructor must throw this exception when a problem occurs. This is how your model object communicates any validation problems to the framework.

The constructor, and its associated validation, is called by WEB4J both when the user POSTs form data and when the columns of a ResultSet are parsed into model objects. WEB4J doesn’t assume that the database holds valid data; after all, databases often hold records that ultimately come from unvalidated sources, such as data load scripts.

Validation can be implemented in any way you want, as long as the ModelCtorException is thrown whenever a problem occurs. WEB4J provides the Check utility class to perform the most common validations. It works with implementations of the Validator interface. It’s not hard to define new, custom Validators for more specific needs.

Hard and soft validation

In WEB4J, validation actually comes in two flavors: hard validation and soft validation. The validation discussed here is soft validation. It’s soft in the sense of providing a “soft landing” for the end user. If a validation fails, then a polished response is sent to the end user. All business validations, such as the ones shown in Spending.java in Listing 10, are soft validations. The failure of a soft validation is expected as a part of normal operation of the application.

Hard validations are more low-level. The failure of a hard validation is not expected during normal operation of the program. Such validations are meant primarily to detect the sort of invalid requests associated with hackers. They also serve to detect egregious programming errors. For an example, imagine a static drop-down list (a SELECT tag), where the OPTION values, set to the range between 1 and 5, are hard-coded directly in the JSP. Because values outside that range could not result from regular user input, and would only arise from malicious intent, it’s appropriate to respond to such input by ceasing processing immediately and providing a curt, unpolished response.

Hard validations are performed early in request processing, before your action is even called. Hard validations are implemented by declaring RequestParameter objects in your action class (see Listing 12 below for more details).

SafeText, Id, and Code

The Spending class uses various building block classes, such as Integer, Boolean, and so on. It also uses some building block classes defined by the framework: SafeText, Id, and Code.

SafeText is used to model free-form user input. It’s recommended as a replacement for String, because in Web applications, Strings are a dangerous substance. They make cross-site scripting attacks possible. These common attacks exploit Web apps that allow free-form user input to be reflected back to users, without disabling executable content (scripts). When reflected back to the user in the view, free-form user input needs to have special characters escaped in order to render scripts unexecutable should they happen to be present. SafeText automatically escapes special characters in its toString() method, so you don’t have to remember to escape the text in the view.

Id is a simple class for any kind of identifier. The main idea here is that identifiers are important for understanding a class; they have a special status and meaning. Thus, it makes sense to use an Id class, to make the item stand out from all the others, and to increase their visibility to the reader.

Code is a class for implementing code tables. It’s available, but you don’t have to use it if you don’t want to. Code tables are very common. They refer to sets of closely related items, much like an enumeration: account types, geographic jurisdictions, and so on. In the Spending class, a Code is used to represent the type of facility — commercial or residential.

The next release of WEB4J will include a Decimal class as an additional building block class; it will be easier to work with than BigDecimal.

The Data Access Object

SpendingDAO.java, the feature’s DAO, is shown in Listing 11.

Listing 11. SpendingDAO.java

package hirondelle.electricity.main.home;

import java.util.*;
import hirondelle.web4j.database.DAOException;
import hirondelle.web4j.database.Db;
import hirondelle.web4j.model.Id;
import hirondelle.web4j.util.Util;
import static hirondelle.electricity.main.home.SpendingAction.LIST_SPENDING;
import static hirondelle.electricity.main.home.SpendingAction.FETCH_SPENDING;
import static hirondelle.electricity.main.home.SpendingAction.ADD_SPENDING;
import static hirondelle.electricity.main.home.SpendingAction.CHANGE_SPENDING;
import static hirondelle.electricity.main.home.SpendingAction.DELETE_SPENDING;

final class SpendingDAO {

  List<Spending> list() throws DAOException {
     return Db.list(Spending.class, LIST_SPENDING);
  }

  Spending fetch(Id aId) throws DAOException {
    return Db.fetch(Spending.class, FETCH_SPENDING, aId);
  }

  void add(Spending aSpending) throws DAOException {
    Db.add(
      ADD_SPENDING,
      aSpending.getDatePaid(), aSpending.getAmount(), aSpending.getKilowattHours(),
      aSpending.getIsEstimated(), aSpending.getFacility().getId(), aSpending.getComment()
    );
  }

  boolean change(Spending aSpending) throws DAOException {
    Object[] params = {
      aSpending.getDatePaid(), aSpending.getAmount(), aSpending.getKilowattHours(),
      aSpending.getIsEstimated(), aSpending.getFacility().getId(), aSpending.getComment(),
      aSpending.getId()
    };
    return Util.isSuccess(Db.edit(CHANGE_SPENDING, params));
  }

  void delete(Id aId) throws DAOException {
    Db.delete(DELETE_SPENDING, aId);
  }
}

It’s important to note that this feature’s persistence layer is implemented with just two items: SpendingDAO.java and statements.sql. That’s it. There’s nothing more to it. There are no XML files used to do any mapping, or anything else. In addition, the methods in the DAO are, in the nominal case, quite compact (usually one or two lines).

As you can see in Listing 11, the DAO class is package-private. In many cases, the methods of a DAO are called only by classes in the same package. If they aren’t required by other packages, then it’s recommended to keep them package-private. Occasionally, DAOs will indeed be used by more than one feature, package, or directory; in that case, it’s perfectly fine to increase the scope to public.

This class was kept package-private by declaring the SqlId objects elsewhere, in SpendingAction. If they were instead declared in SpendingDAO, then SpendingDAO would need to be public. That’s because WEB4J needs to be able to find all SqlIds using reflection on public classes.

The Db utility class is used here in each method. Its methods allow you to implement many operations using a single method call. The Db.list() and Db.fetch() methods perform SELECT operations. Note that they use SqlId objects (LIST_SPENDING and FETCH_SPENDING) to identify a specific statement from your .sql file. Also note that they use a class literal (Spending.class) to define the class of the returned model object.

SQL statements usually need parameters. Such parameters are passed to the methods of the Db class by using zero or more sequence parameters (varargs) at the end of the method call. In Listing 11, the list() operation takes no parameters, while the fetch() operation takes a single Id parameter.

The add() and edit() methods are less satisfying. Because the fields need to be passed in explicitly, the methods are lengthier. (That issue is being addressed in the next WEB4J release.)

Action

The action for this feature is SpendingAction.java, shown in Listing 12.

Listing 12. SpendingAction.java

package hirondelle.electricity.main.home;

import hirondelle.web4j.action.ActionTemplateListAndEdit;
import hirondelle.web4j.database.DAOException;
import hirondelle.web4j.action.ResponsePage;
import hirondelle.web4j.request.RequestParser;
import hirondelle.web4j.database.SqlId;
import hirondelle.web4j.model.ModelCtorException;
import hirondelle.web4j.model.ModelFromRequest;
import hirondelle.web4j.request.RequestParameter;
import hirondelle.electricity.util.TemplatedPage;
import java.util.logging.Logger;
import hirondelle.web4j.util.Util;

public final class SpendingAction extends ActionTemplateListAndEdit {

  public SpendingAction(RequestParser aRequestParser){
    super(FORWARD, REDIRECT, aRequestParser);
  }

  public static final SqlId LIST_SPENDING = new SqlId("LIST_SPENDING");
  public static final SqlId FETCH_SPENDING = new SqlId("FETCH_SPENDING");
  public static final SqlId ADD_SPENDING = new SqlId("ADD_SPENDING");
  public static final SqlId CHANGE_SPENDING = new SqlId("CHANGE_SPENDING");
  public static final SqlId DELETE_SPENDING = new SqlId("DELETE_SPENDING");

  public static final RequestParameter ID = RequestParameter.withLengthCheck("Id");
  public static final RequestParameter DATE_PAID = RequestParameter.withLengthCheck("DatePaid");
  public static final RequestParameter AMOUNT = RequestParameter.withLengthCheck("Amount");
  public static final RequestParameter KWH = RequestParameter.withLengthCheck("KilowattHours");
  public static final RequestParameter IS_ESTIMATED = RequestParameter.withRegexCheck("IsEstimated", "(true|false)");
  public static final RequestParameter FACILITY = RequestParameter.withRegexCheck("Facility", "(d)+");
  public static final RequestParameter COMMENT = RequestParameter.withLengthCheck("Comment");

  @Override protected void doList() throws DAOException {
    fLogger.fine("Listing all Spending items.");
    addToRequest(ITEMS_FOR_LISTING, fDAO.list());
  }

  @Override protected void validateUserInput() {
    fLogger.fine("Validating user input.");
    ModelFromRequest builder = new ModelFromRequest(getRequestParser());
    try {
      fModel = builder.build(Spending.class, ID, DATE_PAID, AMOUNT, KWH, IS_ESTIMATED, FACILITY, COMMENT);
    }
    catch (ModelCtorException ex){
      addError(ex);
    }
  }

  @Override protected void attemptAdd() throws DAOException {
    fLogger.fine("Attempting to add new Spending item.");
    fDAO.add(fModel);
    addMessage("Item added successfully.");
  }

  @Override protected void attemptFetchForChange() throws DAOException {
    fLogger.fine("Attempting to fetch an existing Spending item.");
    Spending model = fDAO.fetch(getIdParam(ID));
    if( model == null ){
      addError("Item no longer exists. Likely deleted by another user.");
    }
    else {
      addToRequest(ITEM_FOR_EDIT, model);
    }
  }

  @Override protected void attemptChange() throws DAOException {
    fLogger.fine("Attempting to change an existing Spending item.");
    boolean success = fDAO.change(fModel);
    if (success){
      addMessage("Item changed successfully.");
    }
    else {
      addError("No update occurred. Item likely deleted by another user.");
    }
  }

  @Override  protected void attemptDelete() throws DAOException {
    fLogger.fine("Attempting to delete an existing Spending item.");
    fDAO.delete(getIdParam(ID));
    addMessage("Item deleted successfully.");
  }

  // PRIVATE //
  private static final ResponsePage FORWARD = TemplatedPage.get("Spending", "view.jsp", SpendingAction.class);
  private static final ResponsePage REDIRECT = new ResponsePage("SpendingAction.list");
  private SpendingDAO fDAO = new SpendingDAO();
  private Spending fModel;
  private static final Logger fLogger = Util.getLogger(SpendingAction.class);
}

Action classes have the most logic. They tend to be branchy, as they must take into account various errors paths. The action class ties all other elements of the implementation together. It uses the model object and the DAO in its implementation.

Action template classes

WEB4J defines an Action interface. In almost all cases, however, you will use an existing implementation of this interface. The ActionImpl base class has basic utility methods for all actions. In addition, there are three ActionTemplateXXX classes, which are useful for common sets of operations. These classes are abstract base classes, and are examples of the Template design pattern.

In the case of SpendingAction, the base class is ActionTemplateListAndEdit, whose abstract methods correspond to the five operations needed by this feature — list, fetch, add, change, and delete. Hence, ActionTemplateListAndEdit forms a natural base class for this feature’s action class.

SqlId declarations

These public static final fields must be declared somewhere in your code, in a public class. The SqlId objects were mentioned earlier, during the discussion of statements.sql.

RequestParameter declarations

These public static final fields form a white list of expected request parameters. If a request contains a request parameter that has no corresponding RequestParameter declared in the action, then an error results. This is intended as a defense against hackers. Request parameter validation is an important defense against malicious attacks. The validations performed using RequestParameter are hard validations.

Message Strings

The calls to addMessage() and addError() pass information messages back to the end user. You might assume that such calls are hard coded, and that the style in the sample code would need to be changed if the app were changed into a multilingual form. That’s an understandable assumption, but it’s wrong. The framework is smarter than that.

WEB4J allows your Java code to remain exactly the same even if you move from one to many languages. The only changes are in the view, where translation tags become necessary. See the sidebar above for more information on how these tags are minimally invasive.

Building model objects

The validateUserInput() method is where the Spending model object is created out of user input. The ModelFromRequest class builds model objects out of request parameters (or any other object). Its build() method takes the Spending.class class literal, to define the kind of model object that should be created. It also takes zero or more sequence parameters (varargs), which are ultimately passed to the model object constructor.

The framework does a number of conversions in the background here. In Listing 12, for example, a DATE_PAID item is passed to the build() method. The class of that object is RequestParameter. However, the object ultimately passed to the model object constructor is a Date, not a RequestParameter. WEB4J figures this out, and performs the necessary conversions in the background. All this allows you to mostly forget about performing repetitive, trivial conversions — like converting from a String to an Integer, or from a String to a BigDecimal.

The ModelFromRequest class relies on another important ordering convention: the order of parameters passed to its build method corresponds one to one with the order of arguments passed to the model object’s constructor. You can verify this by comparing the call to the build method with the Spending() constructor in Listing 10.

Configuring WEB4J

The code discussed so far implements a single feature. When you build a WEB4J app, most of your time will be spent creating features much like this. But there are other items in a WEB4J app that are related not to specific features, but rather to the customization of WEB4J’s behavior. They apply not to a specific feature, but rather to the app as a whole. You can customize WEB4J’s behavior in two ways:

Now, 16 sounds like a lot of interfaces. However, WEB4J provides reasonable default implementations for 10 of them, which can be used by most apps without any configuration effort whatsoever. For the other 6, you must provide an implementation, because no reasonable default implementation is possible. For example, the Electricity Tracker app provides the following classes, placed in the hirondelle.web4j.config package, which configure WEB4J’s behavior:

  • AppInfo.java: Provides the app name, version, build date, and so on.
  • ConnectionSrc.java: Indicates how your app wishes to obtain database connections.
  • ConvertParamErrorImpl.java: Indicates how your app wishes to inform the user about request parameter conversion errors — encountered when converting a request parameter String to an Integer, for example.
  • DateConverterImpl.java: Indicates how your app wants to convert a request parameter String to a Date.
  • Startup.java: Indicates what your app needs to do upon startup.
  • TranslatorImpl.java: Indicates how your app wants to translate text. For a single-language app, the implementation is a simple no-operation.

In conclusion

WEB4J has a philosophy of deep simplicity and minimalism. Its fundamental goal is rapid development, and its intent is to make the task of building plain vanilla HTML interfaces to a relational database as simple and as fast as possible. In addition, WEB4J was explicitly designed to avoid the kinds of pain endemic to many contemporary Web application frameworks.

I hope that this introduction has intrigued you and piqued your interest about this tool. For downloads and more information, check out the WEB4J project Web site.

John O’Hanley is the author of javapractices.com, and the creator of the WEB4J framework. He has been programming for 10 years, and currently lives in Prince Edward Island, Canada.