java hosting


JavaRanch Newsletter Articles in this issue :
Small and Simple Web Applications - the Friki Way (Part 5)Frank Carver
Printable Version
The Coffee House - Reading Patterns in the Coffee GroundsSolveig Haugland
Printable Version
An Introduction to JSTLSue Spielman
Printable Version
The Big Moose Saloon Question and Answer of the Month: To Bean or not to Bean?Dirk Schreckmann
Printable Version
Movin' them Doggies on the Cattle Drive Pauline McNamara
Printable Version
Book Review of the Month - JDK 1.4 Tutorial by Gregory M. TravisRob Ross
Printable Version
Book Promotions For September Thomas Paul Printable Version

Small and Simple Web Applications - the Friki Way (Part 5)

Frank Carver, August 2003

Abstract

This article is the fifth of a series which laments the bloated and unmaintainable state of so many J2EE web applications and looks at ways to keep web applications small, simple and flexible. The series uses the author's Friki software as a case study, and discusses ways to design and build a powerful, flexible and usable web application in less space than a typical gif image.

This article carries on working through the user requirements and finally deploys a working web application, even though it doesn't have all the features of a real Wiki yet.

Introduction

If you've read the first, second, third and fourth articles, you should be aware that the aim of this project is to develop a small, simple and understandable "Wiki" (editable web site) application. We've considered and decided to defer the relatively heavy decision of how to store and retrieve pages by introducing a Java interface, decided on and implemented a simple "template" system to display pages, and are building a supporting test suite and an automated build script as we go along. Last session we created our first web application "war" file.

First, let's recap what our "customer" wants, and what we have got so far:

  1. each page may be viewed using it's own unique URL
  2. page content may contain links to other pages by name DONE
  3. links to nonexistent pages will be marked with a "?" DONE
  4. page content may be created and edited using just a browser
  5. at least 100 different pages can be stored

What do we write next?

We seem to be making serious progress; carrying on with this we now need to decide which of the remaining stories is the one to go for. I'm still not sure about story five, and story four seems to need story one to already be in place if we want it to make much sense. So story one is the one to go for. Note at this point how this process of re-evaluating our priorities at each point has led to a somewhat unexpected order of development. We've learned as we have gone along

Third task: each page may be viewed using it's own unique URL

Usually, we start by adding a new test. Up until now the tests have been a few lines of code run by JUnit, but if we want to test that we can fetch pages from a URL it's not quite as simple as that. Time to stop and think a little.

There is a really strong temptation about now to just press on with developing the "simple" servlet code we need to see something on a browser screen. It's a great feeling when you get to that stage, and the desire to run these last few steps is almost overwhelming. But fight that temptation. We need tests that cover all of our code, especially code that's hard to refactor like web pages and config files.

So, we are agreed that we need tests for the web interface. Luckily, because we haven't written the code yet, we are free to build an interface that's as easy to test as possible. How about we first test for the existence of a specific page URL, then we test that it has the correct contents. This is a new departure for our test cases, so I think a new test fixture class is in order:

AllTests.java

...

public class AllTests extends TestCase
{
    public static TestSuite suite()
    {
        TestSuite ret = new TestSuite();

        ret.addTest(new TestSuite(EmptyTest.class));
        ret.addTest(new TestSuite(TemplateTest.class));
        ret.addTest(new TestSuite(LinkTest.class));
        ret.addTest(new TestSuite(WebPageTest.class));

        return ret;
    }
}

That won't compile until we create the class so we'll start with a simple one:

WebPageTest.java

package tests;

import junit.framework.*;

public class WebPageTest extends TestCase
{
    public void testEmpty()
    {
    }
}

Compile and run this by typing "ant". Does it work? Sort of. We now have 12 passing tests, but the most recent one doesn't actually test anything. Still, it's a start.

Testing a Web Application

There are several tools for testing web applications, but the one I use most often is HTTPUnit. Strictly it's a bit more powerful than we need for just testing the existence of a web page, but I'll bend my own rule here because I know we'll need to test the content and navigation of pages later.

To use the HTTPUnit test tools to our project we need to add some "jar" files to a class path. To avoid version conflicts with other applications, I suggest you add the jar files to just this project for the moment. If you find HTTPUnit useful, you can always add them to a shared class path later.

  1. Download the latest HTTPUnit from sourceforge
  2. Unzip the downloaded archive to a temporary area somewhere
  3. Make a new directory in the "friki" project area next to "src" and "build", call it "lib"
  4. Copy the files httpunit/lib/httpunit.jar, httpunit/jars/nekohtml.jar and httpunit/jars/xercesImpl.jar to the new project "lib" directory.

To update our Ant build script to make these new facilities available to our unit tests, we need to add this new "lib" directory to the test class path declaration:

build.xml

...

  <path id="testclasspath">
    <pathelement location="build/delivery/classes"/>
    <pathelement location="build/tests/classes"/>
    <fileset dir="lib">
      <include name="*.jar"/>
    </fileset>
    <pathelement path="${java.class.path}"/>
  </path>

...

Now we just need to code our first real web application test case:

WebPageTest.java

package tests;

import junit.framework.*;
import com.meterware.httpunit.*;

public class WebPageTest extends TestCase
{
    WebConversation http;
    WebResponse response;

    public void setUp()
    {
        http = new WebConversation();
    }

    public void testApplicationPresent()
        throws Exception
    {
        response = http.getResponse(
            new GetMethodWebRequest("http://localhost:8080/frikidemo"));

        assertEquals("application root page should return a code of 200 (success)",
            200, response.getResponseCode());
    }
}

Note that this code is actually pretty simple. The "setUp" method creates a HTTPUnit "Conversation" class which manages all the HTTP requests and responses for us. Our initial test method creates a "GET" request for a particular URL and fetches the response. Then we are free to use regular JUnit assert methods to check all sorts of things about the response. In this case all we are looking for is a HTTP 200 status. code indicating that the page exists.

However, unless you have already built and deployed a web application called "frikidemo" to a server running on port 8080 of your local machine, then this test fails. If you look in the test log you should see something like:

    Testcase: testApplicationPresent took 2.125 sec
        Caused an ERROR
    Connection refused: connect
    java.net.ConnectException: Connection refused: connect
That's cool, though, we haven't built anything to pass the test yet.

The first stage in making a web application to pass our test is to get a server in place to run our shiny new application. As this is both a readily identifiable step in the process, and something it's easy to get wrong (or just plain forget) later, I suggest we add a specific test for it. That way, if/when something else fails we'll easily be able to tell if it is a problem with the server or with our application.

WebPageTest.java

...
    public void testServerPresent()
        throws Exception
    {
        response = http.getResponse(
            new GetMethodWebRequest("http://localhost:8080/"));

        assertEquals("server root page should return a code of 200 (success)",
            200, response.getResponseCode());
    }
...

This may not feel much like progress. We now have two failing tests instead of one!

    [junit] Tests run: 13, Failures: 0, Errors: 2, Time elapsed: 3.891 sec
The good point of this, though, is that we can address the first problem of getting the server going without forgetting what that is for. Those nagging test failure messages have become a kind of "to do" list.

Making it Work, Part 1: Installing a Local Web Server

Unfortunately, I don't really have the space here to cover installing a web server. Many of you will already be familiar with doing this, or have a local server running already. If this is all new, I recommend downloading and installing the Resin or Tomcat servers.

Come back here when "testServerPresent" passes!

Making it Work, Part 2: Deploying a Web Application

So now we have a server, but one test still fails. If you look at the test log now, you should see something like:

    Testcase: testApplicationPresent took 1.265 sec
        Caused an ERROR
    Error on HTTP request: 404 Not Found [http://localhost:8080/friki]
    com.meterware.httpunit.HttpNotFoundException: Error on HTTP request:
        404 Not Found [http://localhost:8080/friki]
This is a good sign. It shows that the server is running and has correctly responded with the correct HTTP error code (404) for an attempt to access an unknown page. Even better, it shows that HTTPUnit is working, and has correctly recognized the 404 as a "Not Found" error code.

To make this test work we need to "deploy" our web application to the server. You may recall that last session we built a "war" file for just this purpose. The simplest way to "deploy" a war file to a server on the same machine is just to copy the file to the correct location. Long ago we decided to automate all of this, so we need to add some code to our Ant build file to do this for us:

build.xml

...
  <target name="deploy-war" depends="build-war">
    <copy file='frikidemo.war' todir='c:/temp/webapps'/>
  </target>
...

Great. Works for me. I run ant test and all the tests pass.

But I bet it doesn't work for you. I happen to have configured my web server to use the directory c:\temp\webapps for its "war" files, but your server is almost guaranteed to be different. You may be running different server software, you may have configured it differently, you may be running on a system where c:\temp\webapps doesn't even make sense.

We could just require everyone to tinker with their Ant build file before deploying the application, but that seems very clumsy. Luckily Ant has a way round this. Ant can look in an external "properties" file for stuff like this, and we can then gather together anything that might vary between installations in one easily-editable place.

Add a "property file" declaration to the build file:

build.xml

<project name="friki" default="build" basedir=".">

  <property file="local.properties"/>

  <path id="classpath">
    <pathelement location="build/delivery/classes"/>
    <pathelement path="${java.class.path}"/>
  </path>
...

Update the deploy-war target to use a property instead of a literal directory name:

build.xml

...
  <target name="deploy-war" depends="build-war">
    <copy file='frikidemo.war' todir='${deploy.dir}'/>
  </target>
...

And finally, create a new file in the same directory as build.xml:

local.properties

deploy.dir=your real deployment "webapps" directory

If all went well, you should now be able to type

    ant deploy-war test
And all 13 tests should pass.

Looking at Building and Testing Again

You may have noticed something weird going on while you were following on with that last section. Although we have Ant build targets for each of our operations (clean, build, test, build-war, deploy-war etc.) they don't seem to work together very well. If you run ant clean before ant build-war it doesn't work. If you run ant build before ant deploy-war it tries to run some tests that need the application to be deployed. We've got in a tangle.

In a previous session we decided that our eventual goal was to be able to type just ant and have the build script do everything, in the right order. We need to sort it out. One problem is that we have added a bunch of new targets without relating them to each other. Another (and trickier) problem is that some of our unit tests just need our application code, but others need the application to have already been built and deployed.

We could just move all the testing until after the application is built and deployed, but this would really slow us down. Imagine if every time we make even a trivial change to one of our classes we had to completely build all the classes, package everything into a war file, deploy the war file to the server, and wait for the server to open and install the new application. It would be much easier not to bother with testing, wouldn't it?

A better solution is to split the unit tests into two groups. The first group are the tests which just need the code. These should run as soon as possible, and stop the build if they fail. There's no point building and deploying an application if we already know that some of the code is wrong. The second group of tests are the ones which require a running server with a deployed application.

Step One. Split the Unit Tests

Replace our existing AllTests.java with two new files:

LocalTests.java

package tests;

import junit.framework.*;

public class LocalTests extends TestCase
{
    public static TestSuite suite()
    {
        TestSuite ret = new TestSuite();

        ret.addTest(new TestSuite(EmptyTest.class));
        ret.addTest(new TestSuite(TemplateTest.class));
        ret.addTest(new TestSuite(LinkTest.class));

        return ret;
    }
}

RemoteTests.java

package tests;

import junit.framework.*;

public class RemoteTests extends TestCase
{
    public static TestSuite suite()
    {
        TestSuite ret = new TestSuite();

        ret.addTest(new TestSuite(WebPageTest.class));

        return ret;
    }
}

Step Two. Split the Test Build Scripts

Replace our existing "test" target with two new ones:

build.xml

...
  <target name="local-test">
    <junit fork="yes" haltonfailure="yes" printsummary="yes" dir="build/tests">
      <jvmarg value="-Djava.compiler=NONE"/>
      <test name="tests.LocalTests"/>
      <classpath refid="testclasspath"/>
      <formatter type="plain"/>
    </junit>
  </target>

  <target name="remote-test">
    <junit fork="yes" haltonfailure="yes" printsummary="yes" dir="build/tests">
      <jvmarg value="-Djava.compiler=NONE"/>
      <test name="tests.RemoteTests"/>
      <classpath refid="testclasspath"/>
      <formatter type="plain"/>
    </junit>
  </target>
...

Step Three. Reorganise the Target dependencies

We really only want two "public" targets. One (clean) to remove anything generated by the build process such as class files, war file and so on. One (build) to completely compile, package, deploy and test the application:

build.xml

...
  <target name="build" depends="compile,local-test,deploy-war,remote-test"/>
...

As a final test, type ant clean build and watch all the targets roll by successfully.

Back to the task: each page may be viewed using it's own unique URL

We now have a sweet setup to automatically build, deploy and test our application, so we really ought to get back to make some progress with the user goal that we are working on. As developers we know we've made huge progress, but until our customer can see something working, we might as well have stayed in bed.

As always, we will start with a test. But to write a test which fetches a page from a URL we really need to we need to decide what URL to fetch. Different Wiki software addresses this problem in different ways. Some pass the page name as an argument to the code like http://www.ugh.com/wiki/show?page=SomePage Some encode the page in the path like http://www.ugh.com/wiki/pages/SomePage?action=show Some have long URLs with session ids and user preferences and stuff. It doesn't seem to make a lot of difference, so for this I'll just choose the one that seems the simplest for the moment - passing the page name as an argument.

WebPageTest.java

...
    public void testExamplePage()
        throws Exception
    {
        response = http.getResponse(
            new GetMethodWebRequest("http://localhost:8080/frikidemo/show?page=ExamplePage"));

        assertEquals("example page should return a code of 200 (success)",
            200, response.getResponseCode());
    }
...

Sure enough, our test fails. We have no page to show:

    Testcase: testExamplePage took 0.031 sec
        Caused an ERROR
    Error on HTTP request: 404 Not Found [http://localhost:8080/frikidemo/show?page=ExamplePage]
    com.meterware.httpunit.HttpNotFoundException: Error on HTTP request:
        404 Not Found [http://localhost:8080/frikidemo/show?page=ExamplePage]

Note that we have changed the name of the test suite classes, so you will need to look in the file TEST-tests.RemoteTests.txt to see this error.

Let's build a quick servlet class which does nothing except tell us which page we requested:

ShowServlet.java

package friki;

import java.io.IOException;
import java.io.Writer;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ShowServlet
    extends HttpServlet
{
    public void doGet(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException
    {
        String name = req.getParameter("page");
        Writer writer = res.getWriter();
        writer.write(
            "<html><head><title>Page " + name + "</title></head>" +
            "<body>This is Page '" + name + "'</body></html>");
        writer.flush();
    }
}

We also need to tell our web application to map this servlet to the "show" URL. For this we need to create a web application configuration file WEB-INF/web.xml. Create a new directory in friki/src called "files". Create a new directory inside that called "WEB-INF", and create the following file in that new directory:

web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <servlet>
    <servlet-name>show</servlet-name>
    <servlet-class>friki.ShowServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>show</servlet-name>
    <url-pattern>/show</url-pattern>
  </servlet-mapping>

</web-app>

However, we also need to include it in our war archive, which needs a bit more build code. Add the following lines at the start of the "build-war" target to copy anything from the "files" directory to where we make our war file from:

build.xml

...
  <target name="build-war">
    <mkdir dir='build/war/'/>
    <copy todir='build/war'>
      <fileset dir='src/files'/>
    </copy>
    <mkdir dir='build/war/WEB-INF/classes'/>
    <copy todir='build/war/WEB-INF/classes'>
      <fileset dir='build/delivery/classes'/>
    </copy>
    <jar jarfile='frikidemo.war'>
      <fileset dir="build/war"/>
    </jar>
  </target>
...

Now, finally, we have a web application which does something!. It should pass all the tests, and if you send your browser to http://localhost:8080/frikidemo/show?page=ugh you should see "This is Page 'ugh'".

If you like, you can think about what other unit tests might be appropriate at this point, but I reckon it's a good place to stop for the moment. I think we've met our next goal. Do you?

How are We Doing?

We still haven't made a Wiki yet! But we have just about completed another of our user goals, and have built, deployed and even run a web application that we have built from scratch. Best of all, we didn't abandon our commitment to testing everything (even things that seemed easy to write and hard to test). We can say with confidence that whatever we do next, it won't sneakily break what we have written so far.

Next session we will press on with the customer goals and see if we can get to something actually useful to give to our customer as a first real release of the software.

Discuss this article in The Big Moose Saloon!


Return to Top

Sid, the Depressed Cowboy

The Coffee House

by Solveig Haugland

This Month's Story: Reading Patterns in the Coffee Grounds

It was a dark and stormy night at the Coffee House, full of dark and stormy ranchers. They were road-weary from being out on the cattle drive trail all day, some of them with just a few cattle here and there, making the long trip from Deadwood up to Dodge City. Lacey and Brenda were knee-deep in wet, smelly ranchers in their long oilskin coats and longer oily hair and beards. But on the bright side, as Brenda remarked to Lacey when they occasionally passed while serving drinks, these guys were sure ordering a lot of froofroo drinks, and the coffee house got a really good profit from those.

"I shore am tired of being on the road all the time," sighed on of the strangers.

"Me too," sighed another one from across the room, with a full whipped cream mustache and a little bit of cinnamon in his hair from his cocoa-cummulonimbus frappe. "It shore is tiring, having to take two or three cattle to town every day or so, just when somebody orders one."

"It shore is. And there's so many of us on the road, that it seems like there's always a traffic jam," sighed a third, who oddly was wearing a nametag saying 'Hi, My Handle is Tex'. "Heck, I'm just going into town without any cattle at all, just to get the phone number of our grocer, Mr. Plantagenet."

"Hell, Tex, don't you have his phone number already?"

"Well, sure I do, Randy, but how do I know if it's the latest phone number? Gotta make sure I have the latest number."

"I ain't so concerned about the newest phone numbers." A young cowboy in the back spoke, who looked more like a city slicker than a rancher. "But dang, I wish I didn't have the phone books for the entire country in my backpack. Makes for tiring walking. But, you know, I might have to call someone, so I keep carrying'em along."

Lacey and Brenda glanced across the room at Sid and Zeke, sitting in a corner sipping their plain black coffees. Brenda stage-whispered "What in tarnation? That's ridiculous, making the trip down that long trail just for a phone number."

Sid nodded and Zeke rolled his eyes.

"It shore is a recurring problem," agreed Spike, the first one to speak. "Wish we had a solution."

"I tell you another recurring problem I have," added Tex. "I've got some carrier pigeons I raise that I keep in the same pen, of course, as all my cattle. And every time I go in to check on a cow or feed them, those pigeons get spooked and fly away! I spend half my time chasin' after them pigeons. Nearly every day, too."

"Man, do I know what that's like," chimed in Spike. "I've got camels that I keep in with my cows, and I have to go in and feed the cows around about three or four times a day, but I feed the camels maybe once a week. But dang, them camels get all nervous and some of'em just escape whenever I go in that pen."

Brenda and Lacey started giggling, and Zeke and Sid started studying their newspaper awfully closely.

"I'll tell you what's a real recurring problem," said Tex. "I am so tired of filling up those 13 bins of feed around my place. I got one by the house, one by the outhouse, one by the corral, one halfway to town, and a whole bunch more. I gotta maintain that corn in thirteen places, dangitall. And I gotta keep corn, and camel food, and camel goodies, and donuts for me, all in every single one of them locations. I'm surprised I get any work done at all!"

The four locals just couldn't help themselves anymore. RINGADINGADINGADING!!!! The bell over the coffee bar rang and Brenda sang out, "All right, you sorry excuses for ranchers, just you stop spouting off your recurring problems and you turn around up here. We all are gonna show you some reusable solutions. C'mon up here, Sid, Zeke, if you remember anything from that patterns class we went to a couple months ago."

Sid and Zeke hurried up to the front. "What are we gonna teach'em, Lacey?" asked Sid. "Are we done gonna convert MVC or Mediator for'em? I'm not sure I remember all the patterns well enough to tell'em, especially not to a bunch of sorry ranchers like these."

The sorry ranchers picked their grizzled jaws up off the ground, and started protesting that they weren't sorry excuses, but Brenda shooshed them and they shooshed. "You just sit tight. We're going to tell you how to solve all this tarnation."

Lace turned to Sid, "No, Sid, we aren't gonna convert all them patterns to cattle ranching. We're just going to tell'em what the principles are. That's all. You remember them principles?"

"I do!" yelled Zeke, happy to have something to do. "Give me that trollopy face-painting lipstick of yours, and I'll write'em up here on the mirror."

And he wrote the following up on the mirror, above the newfangled latte machine. He had surprisingly elegant handwriting for an old cowpoke.

Them Four Principles of Patterns (Which Be Reusable Solutions to Them Recurring Problems)

  1. Stay off the network! (Or in other words, don't go on the road to town every ten minutes, especially for stuff you don't need that bad! Just wait for a month or two and then take all your cattle to town. And for stuff like phone numbers, don't worry so much about having the last information.)

  2. Software is fragile - divide up unrelated stuff so you don't break parts that you don't mean to. (In other words, keep your pigeons in a separate pen, and keep the camels and other stuff on a different feeding schedule separate from the regular feeding schedule varmints.)

  3. Minimize the amount of information stored in any piece of code. (In other words, just carry around a list of the phone numbers you call the most, don't carry around the phone books for the whole darned country!)

  4. Minimize duplication. (In other words, just have one or two locations for all that feed, and keep seedcorn in one and camel food in another.)

Y'all see what we're saying?"

Tex, Spike, and most of the rest of the group were still staring kinda stupid-like but the young city-slicker lookin' cowboy in the corner spoke up.

"Why, you're telling us that everything we done is wrong!"

There was a threatening murmur from the audience, and the four at the front ganged together a little closer.

"No, no," smiled Brenda. She gave them the works: the smile, the hair toss, the pat on the knee to a couple of'em in the front row. "We done all these mistakes too! But we done gone to a class in th city and we got a whole lot better at a lot of stuff."

There was some mollified grumbling, but it was clear the tide had turned.

"That's right," nervously added Sid. "There's a whole bunch of fancy patt-terns that we learned, but really they all come down to these four things. Stay off the net - umm, the road; don't put stuff together that doesn't belong together; don't carry around information you hardly never need; and don't keep a lot of copies of the same thing all over the place."

"That's all?"

"That's all!" chorused Brenda and Lacey.

The visiting ranchers, sparkles in their eyes and a bounce in their step, rose to their feet. "That' all! Jest four things, that's all!" And the joined voices in a rousing chorus of "Don't Fence Me In," but intuitively realizing that the right thing patterns wise to sing would be "Please Fence Me In," they moved out the door en masse, quickly disappearing into the night with cries of "Don't duplicate!" and "Stay off the trail and/or network!"

"That's an awfully inspiring sight," sighed Lacey as she stared after the last of them. "Say, sir, I didn't catch your name," she said to the young city slicker rancher.

"Alexander," ma'am, he said. "Chris Alexander. I'm helpin' build that big cattle ranch up in Deadwood. You folks have given me an awful lot to think about."

  • Software is extremely fragile. Keep it modular so you don't risk breaking unrelated code.
  • Minimize the amount of information stored by any given code.
  • Minimize duplication.

So, Sid, Zeke, and the rest of the Java ranchers were sitting around, kinda depressed-like, and drinking their coffee a lot blacker than usual.

"Well, at least this ain't gonna ruin the Java crop," sighed Sid.

"Yep."

"Yep."

"That's for sure, Sid! You're darned tootin'!"

Sid looked at him, kinda grumpy, and said, "You know, Tex, you seem awful perky for a guy who's probably losing his hay crop. You got somethin' extra in that coffee?"

"Nope! I just ain't losin' my hay crop."

"Why, you sure are! You're in the same boat as the rest of us."

Tex smiled. "Nope, I'm in a boat of my own. You see, seems like we lose our hay crop maybe every two or three years, more than any rancher really can take. It's a recurring problem, you might say. A recurring problem a lot of us have. And so I says to myself, I says, seems like a guy with any smarts at all could figure out how to not be stuck like this."

Zeke was interested by now, and a little testy and overworked from his triple espresso. "Beggin' your pardon, Tex, but I'm shore you didn't mean to imply the rest of us ain't got no brains."

"Oh gosh, now, Zeke, I didn't mean that at all. I just mean, I started thinkin' about it. I looked at the situation and I says to myself, it rains a lot here in September. That's bad.



Discuss this article in The Big Moose Saloon!


Return to Top
An Introduction to JSTL

An Introduction to JSTL

by Sue Spielman

Have you heard about the JSTL but aren't quite sure of how to make the best use of it? The JSTL: Practical Guide for JSP Programmers covers everything you need to know to get started and become productive using the JSP Standard Tag Library.? The Practical Guide series continues to be packed with information for real developers and is written in a straightforward and easy-to-use manner. The book has just been released and comes with free download of all code examples used throughout. Each standard action is covered with a detailed explanation and includes a code sample so you can start using the JSTL immediately.

The following sections are excerpted from various chapters within the JSTL: Practical Guide for Java Programmers. The selected sections? should give you a taste of what you can learn from the book.? Sue Spielman will be in the JSP forum the week of Sept 30? answering questions and will also be giving away complimentary copies of the JSTL: Practical Guide for JSP Programmers.

Introduction

JSTL is the JSP Standard Tag Library. The JSTL came about under JSR-52 of the Java Community Process (JCP). The specification can be found at http://jcp.org/jsr/detail/52.jsp . JSR-52 covers the creation of a standard tag library for JavaServer Pages and allows this library to be available to all compliant JSP containers. These tag libraries provide a wide range of custom action functionality that most JSP authors have found themselves in need of in the past. Having a defined specification for how the functionality is implemented means that a page author can learn these custom actions once and then use and reuse them on all future products on all application containers that support the specification. Using the JSTL will not only make your JSPs more readable and maintainable, but will allow you to concentrate on good design and implementation practices in your pages. We can finally take the ?custom' out of custom action and replace it with ?standard' .No more creating your own iteration action for the tenth time. Additionally, your favorite Integrated Development Environment (IDE) that supports JSP authoring will now support these standard actions and can assist the JSP page author in rapid development.

Functional Overview

The JSTL encapsulates common functionality that a typical JSP author would encounter. This set of common functionality has come about through the input of the various members of the expert group. Since this expert group has a good cross section of JSP authors and users, the actions provided in the JSTL should suit a wide audience. The JSTL is a set of custom actions that is based on the JSP 1.2 and Servlet 2.3 specifications. While the JSTL is commonly referred to as a single tag library, it is actually composed of four separate tag libraries:

  • Core
  • XML manipulation
  • SQL
  • Internationalization and formatting

These libraries are defined by the Tag Library Descriptor files. Using separate TLDs to expose the tags, the functionality for each set of actions is apparent and makes more

sense. Using separate TLDs also allows each library to have its own namespace. To sum up for now, the layout of the JSTL is straightforward. The overriding theme throughout the JSTL is simplifying the life of the page author. The page author is the person who builds the JSP pages. There has always been a need (although not a requirement) that the page authors have some understanding of a programming language (usually Java)in order to create complex pages. This dilemma is what has hampered the true role separation between the JSP page author and the Java programmer. Using the tags provided in the JSTL, we are closer to reaching that clean division of labor. The functional areas in the JSTL help page authors identify what type of functionality they need and where they can find it.

Using the Expression Language

Before we dive into the various functional areas in the JSTL, we should start with the expression language. As touched on briefly in the first chapter, this is one of the most important features of the JSTL and is a prominent feature of the JSP 2.0 specification.? The expression language (EL) allows for a much simpler syntax for doing application data manipulation for the page author. Currently the EL in the JSTL can only be used with tag attribute values, primarily in actions that reside in the Core tag library. It is possible to use the EL within template text if you are working with the JSP 2.0 specification. Expressions in template text are not supported if you are using JSTL 1.0 with JSP 1.2. What it means to use EL in attributes can be shown in the following example:

<c:if test="${book.orderQuantity >book.inStock}">

The book <c:out value="${book.title}"/>is currently out of stock.

</c:if>

Using the <c:if>conditional tag (which we'll talk about in detail shortly),we can use the EL in the test attribute to determine if we can order a book that is currently in stock. If the book is not in stock, we can access the book Object by using the EL and assigning that to the value attribute. Anyone who has worked with JSPs before can certainly appreciate the ease-of-use and coding simplification possible with the EL. If you are working with JSP 2.0,this sample could also be written using the expression in the template text like:

<c:if test="${book.orderQuantity >book.inStock}">

The book ${book.title}is currently out of stock.

</c:if>

Keep in mind that when using an identifier (like book ,for example) with the EL, it is the same thing as if you had done PageContext.findAttribute(identifier ).The identifier itself can reside in any of the known JSP scopes. This includes page ,request ,session ,or application scope. If the identifier isn't found in any scope, then a null value is returned.

Implicit Objects Available in the EL

There are quite a few implicit objects exposed through the EL. These objects allow for access to any variables that are held in the particular JSP scopes. Objects include pageScope, requestScope, sessionScope, and applicationScope. All of these xScope objects are Maps that map the respective scope attribute names to their values. Using the implicit objects param and paramValues, it is also possible to access HTTP request parameters. This holds true for request header information as well as for using the implicit objects header and headerValues.

The param and header objects are Maps that map the parameter or header name to a String .This is similar to doing a ServletRequest.getParameter(String name) or ServletRequest.getHeader(String name).The paramValues and headerValues are Maps that map parameter and header names to a String[] of all values for that parameter or header. Again, this is as if you had made ServletRequest.getParameterValues(String name) or ServletRequest.getHeaders(String) calls.

The initParam gives access to context initialization parameters, while cookie exposes cookies received in the request. The implicit object pageContext gives access to all properties associated with the PageContext of a JSP page such as the HttpServletRequest , ServletContext ,and HttpSession objects and their properties.

Let's ?look at a couple of samples to drive the usage of the objects home:

  • ${pageContext.request.servletPath} will return the Servlet path obtained from the HttpServletRequest.
  • ${sessionScope.loginId} will return the session-scoped attribute named "LoginId" or null if the attribute is not found.
  • ${param.bookId} will return the String value of the bookId parameter, or null if it is not found.
  • ${paramValues.bookId} will return the String []containing all values of the bookId parameter, or null if it is not found. Using paramValues is particularly useful if you have a form with check boxes or for some other reason a parameter might have multiple values like a multiselect box.

The EL operations are necessary to handle data manipulations. All of the standard and common operators are available. Functionality is included in the EL for relational, arithmetic, and logical operators.

Automatic Type Conversion

The automatic type conversion is a very convenient feature of the EL in that a full set of coercion between various object and primitive types is supported. Coercion means that the page author isn't responsible for converting parameters into the appropriate objects or primitives. The JSTL defines appropriate conversions and default values. For example, a String parameter from a request will be coerced to the appropriate object or primitive.

If we are dealing with A , which is an item or object, the coercion rules supplied by the JSTL will be applied for each given type. These coercions are done under the covers for you by the implementation, but it is always a good idea to understand how, and in what order, the rules are being applied. For this reason, I'm including the coercion rules from the JSTL 1.0 specification in JSTL Reference section so that you can review them if you want.

Let's look at Example 3.1.If we have a variable called myInteger and want to use the value in an expression as a number, we simply declare a variable with the value using <c:set>.If a parameter that represents the month is passed in the request as a String , the value of the month variable will be correct because the String will be coerced to the correct type when used. If the value of the parameter does not parse correctly to a number (say, the value is September instead of 9) at that point an exception will be thrown. Having automatic type conversions can save unnecessary exceptions from happening.

Example 3.1 Performing a Coercion

<c:set var="myInteger"value="${param.month}"/>

<p>

The value of myInteger is:<c:out value="${myInteger}"/>

Perform a multiplication operation to show that the type is correct:

<c:out value="${myInteger *2}"/>

If the coercion is not possible,the exception might look something like:

javax.servlet.ServletException:An error occurred while? evaluating custom action attribute "value" with value "${myInteger *2}":An exception occured trying to convert String "September"to type "java.lang.Double"(null)

Keep in mind that it's possible to use <c:catch>to prevent a complete exception stack from being displayed to the user. The page author can handle an unexpected value more in a user-friendly way, perhaps informing the user of the type of data that is expected or providing a sample of the format of data required by the user. A more graceful handling of an error is shown in Example 3.2.

Example 3.2 Friendly Handling of a Coercion Error

<c:catch var="coercionError">

The value of myInteger is:<c:out value="${myInteger}"/>

Perform a multiplication operation to show that the type is correct:<c:out value="${myInteger *2}"/>

</c:catch>

<c:if test="${not empty coercionError}">

<b>The value of month is supposed to be a number.</b>

Here 's more information on the error:

<br><font color="#FF0000"><c:out value="${coercionError}"/>

</font>

</c:if>

Working with the Core Actions

The set of tags that are available in the Core tag library come into play for probably most anything you will be doing in your JSPs. Let's walk through code samples to see how we use each of the tags provided in this library.

The Core area comprises four distinct functional sections:

  • General-purpose actions that are used to manipulate the scoped variables that might be found within a JSP.These general-purpose actions also encompass error handling.
  • Conditional actions used for doing conditional processing within a JSP.
  • Iterator actions that make it easy to iterate through collections of Objects.
  • URL-related actions for dealing with URL resources in a JSP.

Let's look at each functional section in the Core tag library a bit more closely.

Writing Output to the JspWriter

There are four general-purpose tags. The <c:out>tag is probably the tag that you will see the most. It is used to output to the current JspWriter .This is similar to using the JSP expression <%=scripting language expression %>to write dynamic data to the client.

The value to be written to the JspWriter is specified as a value attribute. You can use expressions in the value attribute. This allows for the resulting evaluation to be sent to the JspWriter. The <c:out> tag can perform XML character entity encoding for <,>,&,", and '.This means that a < will be automatically encoded to &lt;. The XML entity values that are used for encoding the characters are shown in Table 4.1. Therefore it's possible also to use this encoding capability to encode any HTML, like <br>, so that the angle brackets appear correctly. This capability is controlled by the escapeXml attribute. It defaults to true .

It should be obvious that:

The title of the book you just purchased is

<c:out value="${sessionScope.bookInfo.title}">

is much easier to read (and write) than:

<%@page import="com.mk.jstl.bookInfo"%>

<%BookInfo bookInfo =(BookInfo)session.getAttribute"

("bookInfo");

%>

The title of the book you just purchased is

<%=bookInfo.getTitle()%>

In another example, we might want to output some data values that have been stored in a scoped variable called myData .The value of myData is "<b>I love to ride my bicycle</b>". There are HTML tags included in the string that we want to make sure are rendered correctly with the string bolded. To ensure that the data is displayed to the user correctly we would use:

<c:out value=${myData}escapeXml="false"/>

With escapeXml set to false, our users see the correct display with the text bolded.

Otherwise, they just see the characters <b>displayed with the text as shown in Figure 4.1.

The two displays are shown as they would appear if you were to view the source of the resulting file in your browser. The first output is using the default value of escapeXml , while the second output shows the result of using the esacpeXml set to false . With escapeXml defaulting to true :

&lt;b&gt;I love to ride my bicycle&lt;/b&gt;

With escapeXml set to false:

<b>I love to ride my bicycle</b>

Figure 4.1:EscapeXML sample.

Working with the Internationalization and Formatting Actions

More than likely, the application you are developing today will have to be internationalized tomorrow. Wouldn't it be great if the effort required to internationalize your application could be reduced to zero? Well okay, that might be too optimistic to hope for since anyone who has developed applications for international use knows there is always something that needs to be tweaked. Luckily, the internationalization and formatting actions provided in the JSTL are a comprehensive set of actions that can be used to minimize the headaches of having to internationalize your application. These actions come under the functionality of the I18N umbrella. I18N, which refers to the 18 letters between the I and the N in internationalization ,is a common acronym used when talking about internationalization features. It is also common to use the term L10N, for localization. In this chapter, we'll explore these internationalization actions. All of the actions related to I18N are contained in the custom tag library with the URI http://java.sun.com/jstl/fmt and are frequently accessed by using the fmt prefix x.

The I18N functional area can be broken down into two main areas:

1. Locale and resource bundles that include such actions as:

  • <fmt:setlocale>
  • <fmt:bundle>
  • <fmt:setBundle>
  • <fmt:message>
  • <fmt:param>
  • <fmt:requestEncoding>

2. Formatting for numbers, dates, and currency, which includes such actions as:

  • <fmt:timeZone>
  • <fmt:setTimezone>
  • <fmt:formatNumber>
  • <fmt:parseNumber>
  • <fmt:formatDate>
  • <fmt:parseDate>

To address both of these functional areas, let's first take a cursory look at what pieces are involved in creating international applications. Then we'll look at how these pieces can be put to work using the various actions available in the JSTL.

First,the <fmt:message>Action

Before we start talking about the various actions available in the I18N,let's introduce the <fmt:message>action. If you really wanted to do the bare-bones amount of work necessary to build an internationalized application,<fmt:message>is the only action that you'll need to consider. The <fmt:message>action takes advantage of the LocalizationContext (which we talk about in the next section).By using the <fmt:message>,you can output values from your resource bundles as simply as:

<fmt:message key="welcome"/>

The appropriate resource bundle will be used to look up the key "welcome" and the translated string will be provided. This is about as easy as it gets to incorporate international support into your application. The <fmt:message>action also supports parameterized content, also called parametric replacement. For example, you can provide variables that will be used within the string used by the key attribute. Say we want to personalize our welcome page and pass the name of a user so that we can welcome them. To do this, we use the <fmt:param> subtag. We will talk about this in more detail later in this chapter, but as a quick example, so that you are familiar with the format, the action might look like:

<fmt:message key="welcome">

<fmt:param value="${userNameString}"/>

</fmt:message>

In this example, we would be accessing a variable already set, called userNameString , that would then be used as a parameter to the message. If we were accessing the English version of the resource bundle,Welcome Sue would appear in the JspWriter . Now, with the basics of the <fmt:message>under your belt, let's take a more in-depth look at how the I18N actions work.

Author Note: Chapter 6 -? Working with the Internationalization and Formatting Actions continues by going into great detail on how to work with Locales, resource bundles, and all of the I18N standard actions.

Using the SQL Actions

The JSTL includes a number of actions that provide a mechanism for interacting with databases. The previous sentence should, at a very minimum, send up a red flag in your architectural visions. One might ask, "Do I really want to be able to perform SQL actions such as queries, updates, and transactions from my JSP? Isn't that business logic that belongs in the model? The answer is yes. Yes, yes, yes. To follow a Model-View-

Controller (MVC) architecture, which is the predominant design pattern used in building web applications today, you definitely want to keep your model information in your business logic. This means that you don't want it in your JSPs. Why then are these actions even provided in the JSTL? Good question and one that I've discussed with various members of the JSR-53 expert group. The reason is the "C" or community in the Java Community Process (JCP). The community has asked for it, the community has gotten it.

Many feel that for prototyping, small-scale, and/or very simple applications, or if you just don't have the engineering staff to implement a full MVC model, then the SQL actions might prove useful. While I can (barely) see the point being made for use of the SQL actions for prototyping or small-scale applications, I can't ever validate the argument that you just don't have the time to implement an MVC model correctly. If that is the one and only reason why you are choosing to use the SQL actions, then I suggest that you investigate using such frameworks as Struts which is part of the Jakarta projects and can be found at http://jakarta.apache.org/struts/index.html . Struts is an MVC framework that can be learned quickly and will provide a much cleaner architecture than having Model information located throughout your JSPs. For a complete discussion on Struts along with a sample application, refer to The Struts Framework:Practical Guide for Java Programmers, another title in the Morgan Kaufmann Practical Guide series.

If you are careful about how you code your SQL actions, it should be easy enough to pull out the code and put it into classes that represent the Model interaction at a later point. I am not going to go into the various design patterns that can be applied for doing business or integration tier access. But if you consider using the SQL actions in your application, it would be wise at least to familiarize yourself with such common patterns as Transfer Objects, JDBC for Reading, Data Transfer Object (DTO) Factory, Data Transfer Hashmap,and Data Transfer Rowset. Doing so may help you avoid embedding the business logic/data access into your JSPs so deeply that you are left with a tangled mess.

With that said, I don't consider it an architectural flaw to have the SQL actions included in the JSTL. However, I do consider it an architectural flaw to use them in your application development. It is up to the page author and application architect to make sure that the design patterns are being adhered to correctly, if not for the maintenance issue of the application then for the practice of good engineering. However, since these actions are included in the JSTL,I must make sure you understand them and their features so that you can make an informed decision.

The JSTL SQL actions provide functionality that allows for:

  • Making database queries
  • Accessing query results
  • Performing database modifications
  • Database transactions

What all of the SQL actions have in common is that they work against a specific data source.

Let's examine how the data source is set up and configured. We'll then go through the other configuration settings as well as the available interfaces. Then we'll a look at how to use the actions in situations where their use would be appropriate.

The Available <SQL>Actions

There are six actions provided in this tag library:

  • <sql:setDataSource> for exporting a variable that defines a data source
  • <sql:query> for querying to the database
  • <sql:update> for updating the database
  • <sql:transaction> for establishing a transaction context for doing queries and updates
  • <sql:param> for setting parameter markers ("?") used in SQL statements.

Author note: I hope that you've found these brief excerpts to be helpful and applicable to your development.

Sue Spielman is president and senior consulting engineer, of Switchback Software LLC, http://www.switchbacksoftware.com . Switchback Software specializes in architecture/design and development of enterprise business and web applications. Sue is the author of ?The Struts Framework: Practical Guide for Java Programmers', ?JSTL: Practical Guide for JSP Programmers' and ?The Web Conferencing Book'. Sue can also be found speaking at various technical conferences around the country. You can reach her at sspielman at switchbacksoftware.com

Discuss this article in The Big Moose Saloon!


Return to Top
The Big Moose Saloon Question and Answer of the Month

Mosey on in and pull up a stool. The JavaRanch Big Moose Saloon is the place to get your Java questions answered. Our bartenders keep the peace, and folks are pretty friendly anyways, so don't be shy!

Over in The OO, Patterns, UML and Refactoring Forum, Dan Malks did a heck of a job giving folks some good advice on the ol' "to bean or not to bean" decision. Following is an excerpt from the original forum thread.

Question: To bean, or not to bean, that is the question.

Answer: A good question to ask yourself is "what are the requirements of my app (functional and non-functional)" and let these answers drive your architecture and design.

As for serializing your object, do you need to transfer information across a physically distributed boundary? ie: is your biz tier remote from your presentation tier? This is one reason you would want to serialize an object...so that you can use it to transfer data across this distribution boundary to reduce the sort of network performance problems that can arise with distributed object invocations. This pattern is called "Transfer Object" and is typically used in J2EE when you are using EJB in a remote biz tier. In this case, you would use a remote Service Facade, called a "Session Facade" to serve as a uniform, course-grained access point into your remote services.

If you do not have a remote business tier, you can still use EJB, using Local Session Beans as your Service Facade for the same purpose. Using Session beans in this way gives you the benefit of using their transaction semantics and declarative security. If you don't need these features, then you can use a Plain Old Java Object (POJO) Service Facade, foregoing the use of EJB entirely. Lots of different options.

Either way, your Service Facade should delegate to Application Service(s) (another pattern), where your common service code resides. Application Services work with Business Objects, which represent your domain model (such as Company in your case).

Question: Should I create a separate class for my db transactions?

Answer: There are numerous approaches to persistence. Separating your data access logic from your Business Object and moving it into a separate class as you suggest is described in the "Data Access Object" (DAO) pattern. There are many other options, as well.

We cover all this information in detail, including design tradeoffs and code samples in our book Core J2EE Patterns, 2nd ed.

Remember, use patterns as a guide and use what works, but don't feel compelled to use a pattern "just because it's there". A pattern is a tool, just like any other and should be used judiciously and appropriately.

Discuss this article in The Big Moose Saloon!


Return to Top
Movin' them doggies on the Cattle Drive

It's where you come to learn Java, and just like the cattle drivers of the old west, you're expected to pull your weight along the way.

The Cattle Drive forum is where the drivers get together to complain, uh rather, discuss their assignments and encourage each other. Thanks to the enthusiastic initiative of Johannes de Jong, you can keep track of your progress on the drive with the Assignment Log. If you're tough enough to get through the nitpicking, you'll start collecting moose heads at the Cattle Drive Hall of Fame.

Fresh riders on the Drive...
Another hardy soul has signed up for the Drive: slide one down the bar to Diego Kappenbach. Have fun Diego, and keep them spurs in tight!

Another moose on the wall for...
Yep, that's right, you make it through, you get yerself a nice little moose to hang on the wall. Well, OK, so it's a virtual moose on a virtual wall, but you can be right proud of 'em! Thanks to the efforts of Marilyn deQueiroz, you can now proudly admire your codin' accomplishments at the Cattle Drive Hall of Fame. Check it out, pardner, it's worth a wink.

Thems mooses are multiplyin' up thar on the wall, that's fer sure. Three cattle drivin' coders bagged their first trophies: David Malott, Greg Neef and Terry Broman. Way to wrastle 'em, guys.

Sherrry Cuenco is moooin right along and has added another fine specimen to her growing collection of moose fur. Fine ropin' thar Sherry, next sarsparilla's on the house!

JavaRanch's one and only hired gun has gone an' done writ all them jdbc programs right good and nit-free! After completing the Cattle Drive we're a hopin' that John Hembree will be hangin' out at the Drive, helping out the greener jungins along the way. Nice job, John!

Saddle Sore...
On the verge of gittin' herself a moose too, we're settin' up some glasses on the bar for Amy Phillips - she's gettin' mighty close to that next moose...

Nitpicking is hard work too...
We know they're workin' reeeeeally hard, after all, we've seen what those assignments look like once the nitpickers have combed through 'em. Hats off to Marilyn deQueiroz, Pauline McNamara, and Jason Adam for their dedication and patience with the pesky vermin that always manage to make their way into those assignments.

New hands pulling the sarsparilla...
Notice the changes behind the bar? We got us some new help fer all that work wipin' the bar and shinin' up the glasses. Welcome to Carol Murphy, one of a fresh batch of bartenders on the ranch. This 'un thought she was done with the Cattle Drive, but we gone and roped her back in! Big thanks to Barry Gaunt, who got his galant spurs caught on the way out and is hanging out a bit longer to keep Carol company behind the bar. Though he'll be off to other locales at the ranch we're countin' on him comin' 'round real regular like. Meanwhile Mr. Michael Matola is out checkin' fences for a while in the far reaches, yep he's on the trail headin' through Spain and will be back sooner an you can say sangria amigo!

Joinin' the Drive
You think ya got what it takes? You ready for some work and some good learnin'? Then pull up a stool and read up on Joinin' the Drive. Good luck!

Cattle Drive update by Ol' Timer Pauline McNamara


Return to Top
Book Review of the Month

JDK 1.4 Tutorial
Gregory M. Travis
    
The book "JDK 1.4 Tutorial" by Gregory M. Travis has proven to be a valuable resource to me over the last year. Even though the 1.4 version of the JDK has been out for a while now, many applications and development teams are still using the 1.3 JDK. When they finally make the switch to 1.4, this book is all they need to quickly bring them up to speed on all the goodies that the new JDK has to offer.

Note that this book is NOT a general-purpose java tutorial; there are plenty of other books that cover that topic. As the author states, "The ideal reader of this book is an intermediate or expert Java programmer who needs to use the new features of JDK 1.4." The book covers NIO (New Input/Output), Java Web Start, Logging, Assertions, Regular Expressions, Preferences API, and the Secure Socket Extension. It also covers the changes and additions the 1.4 JDK introduces to the Java2D and Collections API, and enhancements to the Exceptions mechanism.

The book covers these topics in great detail and provides source-code examples for how to best use the new features. In my own development work, I have found the chapters on Advanced NIO, Assertions, and the Preferences API most valuable, and the examples in the book allowed me to very quickly implement the new functionality in my applications.

I have used the book both as a tutorial for the new 1.4 features, as well as a reference source for exploring the detailed intricacies involved in using the new APIs and in both cases I have found the tone and style of the author's writing to effectively convey the information in a clear, concise manner.

If you are an experienced Java developer and want to learn about all the new features in the 1.4 JDK, this is the book for you. It's well written, includes extensive coverage of the new features, and doesn't waste any pages with "HelloWorld" tutorial programs that are covered already in countless beginner's Java books.

(Rob Ross - Bartender, August 2003)
    
     More info at Amazon.com || More info at Amazon.co.uk

    

    

Other books reviewed in August :

LDAP Programming, Management and Integration by Clayton Donley
Applying Enterprise JavaBeans by Vlada Matena, Sanjeev Krishnan, Beth Stearns
Jakarta Pitfalls by Bill Dudney and Jonathan Lehr
Test Driven Development: A Practical Guide by David Astels
Mac OS X for Java Geeks by Will Iverson
GUI Bloopers by Jeff Johnson
JDK 1.4 Tutorial by Gregory M. Travis
In Search of Stupidity: Over 20 Years of High-Tech Marketing Disasters by Merrill Chapman
The Rational Unified Process Made Easy by Kroll and Kruchten
Bitter EJB by Bruce Tate, Mike Clark, Bob Lee, Patrick Linskey
We Blog - Publishing Online with Weblogs by Paul Bausch, Matthew Haughey, Meg Hourihan
Effective Project Management by Robert K. Wysocki, Rudd McGary
Eclipse: Step-by-Step by Joe Pluta
Web Services Patterns: Java Edition by Paul B. Monday
Java Persistence for Relational Databases by Richard Sperko
AspectJ in Action by Ramnivas Laddad
Hacking the XBox. An Introduction to Reverse Engineering by Andrew "bunnie" Huang
Code Generation in Action by Jack Herrington
Running Weblogs with Slash by chromatic, Brian Aker, Dave Krieger
Jess In Action by Ernest Friedman-Hill
Linux in a Nutshell by Ellen Siever, Stephen Figgins, Aaron Weber
Discuss this article in The Big Moose Saloon!


Return to Top
Scheduled Book Promotions for September :
September 9 UML for Java Programmers Robert C. Martin Prentice Hall OO, Patterns, UML and Refactoring
September 16 AspectJ in Action Ramnivas Laddad Manning Other Java Products and Servers
September 23 Eclipse Modeling Framework Frank Budinsky, Dave Steinberg, and Ed Merks Addison Wesley Professional IDE's and other tools
September 30 JSTL: Practical Guide for Java Programmers Sue Spielman Academic Press JSP

Return to Top
Managing Editor: Dirk Schreckmann

Comments or suggestions for JavaRanch's NewsLetter can be sent to the Newsletter Staff.

For advertising opportunities contact the Newsletter Advertising Staff.