|
Articles in this issue :
|
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:
- each page may be viewed using it's own unique URL
page content may contain links to other pages by name
DONE
links to nonexistent pages will be marked with a "?"
DONE
- page content may be created and edited using just a browser
- 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.
- Download the latest HTTPUnit from sourceforge
- Unzip the downloaded archive to a temporary area somewhere
- Make a new directory in the "friki" project area next to "src" and
"build", call it "lib"
- 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
|
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)
-
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.)
-
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.)
-
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!)
-
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 <.
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 :
<b>I love to ride my
bicycle</b>
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 :
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.
|