I encountered a very sort of weird thing that I found little
assistance for on the Web, so I thought I'd write out what
I discovered in case anyone else has a similar issue.
This is a lesson in the pain of violating tiers in your design.
This is a strange tale of an IBM driver and how an EJB container
works and how apparently misleading or at least apparently unrelated
issues end up making sense and being related.
I have a Session EJB running on WebLogic 9 which implements
the TimedObject interface available since EJB 2.1. The purpose
of the bean is to execute a SQL statement every morning at 4 am.
The database against which the SQL statements are run is DB2
on an IBM iSeries. So I'm using IBM's jt400.jar driver to do this.
I had set up JDBC DataSource in the WebLogic console, and it was
working great for a couple of weeks.
One night before leaving work, I quickly added some boilerplate SQL Statements
to my Timer bean, which were placeholders for when I got the real files in the
promoted library later on. I wanted to be done with the use case, and move on,
but I didn't have the real files yet, so I put in this placeholder.
Suddenly my entire WebLogic server would not start.
This was what I added to my DAO:
private static final String GET_PRODCODES = "SELECT PRODCODE FROM POPRODLB.PPPRDPF";
What about writing a standard SQL Statement like that would cause
the entire server not to start?
WebLogic says that the reason it would not start is because I was suddenly
throwing a HeadlessException. This is a very rare exception that was added
to Java 1.4. It is meant to be thrown when you try to make a connection to some
user input device, such as a mouse or keyboard or display, but the system has no
user input devices available or graphic libraries to route the message through.
I could not understand how my simple, apparently innocent statement could
bring down my entire server. Here's the weird answer:
I knew it had to be something in the SQL statement, since that was the only
thing that I had touched. Now a lot of people use this box, so it was possible
that there was some inadvertant side effect from some other app, but that seemed
like the sort of lame thing programmers tell themselves in the hopes of not
being responsible for the issue.
But 1) There's nothing wrong with that SQL statement and so why would there
be an exception at all and 2) how would the container know because I am not
executing this bean right now--it's not 4 in the morning. I am just trying to
start the server.
When you start WebLogic 9.0, it loads your EJBs and does a trial execution of
your all of your EJBs methods so that it can guarantee that it is at least
possible to run them all properly. It was not able to run some method properly,
so it was refusing to start the server.
What was I doing to cause a HeadlessException though? That's what the stack trace
said. Not knowing what the issue was, but isolating what I had changed (which must
then be the cause of the problem), I commented out my SQL call. My server started.
I am executing a number of queries, using the same DAO, and this is pretty standard
stuff. So I looked at my SQL statement again. The "PPPRDPF" file referenced in the
query was named wrong. The real name is "PPPRODPF". I didn't pay much attention
to the name because I knew that the files would be renamed and would move anyway
for the new version of the application I was writing.
But WebLogic not only ensures it can invoke the methods, it executes the methods
without committing. which includes running the SQL statements.
So that is a reason why I love WebLogic. But it also means that my improperly named
table, which should cause a SQL exception to be thrown, is throwing HeadlessException.
Now it is true that I am executing the statement via this DataSource running on a
Linux box with RedHat Enterprise and no mouse or keyboard or display connected to it,
and no GUI libraries.
It had to be the driver. So I asked my friend Kevin about it, and he said that the
driver might be throwing that exception; he recalled seeing that before.
It turns out that the way IBM wrote the jt400 driver, it pops up a dialog box when
a SQLException is generated, indicating the getMessage() text.
So I changed the name from "PPPRDPF" to "PPPRODPF" and everything worked perfectly and
my server started.
There are two problems here:
1) With release of Java 1.4, we gained the ability to chain exceptions. This is a good
practice, and one should always do it. IBM was not chaining their exceptions. There
was no indication that, at root, the problem was a SQLException. The HeadlessException
was the only thing on the stack.
So the exception lesson is: always chain your exceptions so that the user can later
unravel the nested exceptions and find the root of the problem, and not chase their
tail on some epiphenomenal, unrelated matters.
2) Don't violate tier boundaries. In an enterprise Web app, there is a right way to design
your tiers. Using JavaServer Faces and EJBs, it probably looks like this:
Tier 1: Presentation (JSPs) and View Helpers (JSF Backing Beans).
Do not put any logic in your JSF backing beans (or Struts actions or equivalent) that is
not merely related to
1) translating your models into JSF-required models. For example, you need code like this:
final DataModel dm = new ListDataModel(userDelegate.findEditableUsers(getCurrentUser()));
to turn your basic models into JSF-specific things like SelectItems or DataModels, so they
can be displayed.
or
2) Managing the user view.
For example, your Backing Bean should have methods like this:
@Override
public boolean isRenderEditButton(){
return !isUserInRole(SuperUser);
}
to determine whether or not to show some panel grid, etc.
That should be all you do in a backing bean or Struts action. Business logic other
than that required to gather the view doesn't belong here.
Tier 2: Delegates
The Business Delegate pattern indicates that you do two things here:
1) Use an implementation of the ServiceLocator pattern to find your services (EJBs or
Web Services or what have you) and
2) Wrap any exceptions, like RemoteException, that they might throw into more
general application exceptions.
This is all standard stuff. But you don't do anything else in the tiers. You're aiming
for high-cohesion. That is, have a class or type of class be responsible for just one
job. Notice that the apparent two jobs of Business Delegate are really one job: encapsulate
how the service tier is implemented.
No business logic belongs here. No presentation knowledge whatsoever should be present here.
Tier 3: Service
This is your implementation of business logic, often EJBs or POJOs or WebServices or whatever.
Just hide it. Do your work there. Use a rules engine like drools or the Interpreter pattern
or the Visitor pattern or whatever you like to apply business rules here.
I like Visitor for this, but I recognize that I am in the minority perhaps. Visitor is nice
because you can represent a one-to-one translation between a business rule written by an
analyst and a single visitor class. But that's for another topic.
Your EJBs should work identically if your client is a Faces app or a Struts app or a Swing app.
Tier 4: Data Access
These are DAOs that know what database they are talking to. Use a DataSource.
DAO methods look like this:
public List<Manufacturer> getManufacturers() throws PersistenceException { ... }
Notice two things:
1) We're returning a list of models.
2) We're wrapping the SQLExceptions that our RDBMS throws with a custom exception called
PersistenceException, in order to hide the implementation. That is, to hide the fact that
it is an RDBMS that we are using to persist data.
If you change your database to a flat file, you would have to change code in Tier 3 (your EJBs).
That would be bad. If you wrap your exceptions, (chain them), then you haven't violated
the tier boundary, and your code is fine. If your new flat file access code starts throwing
FileNotFoundException, you're still wrapping those with PersistenceExceptions, so your
clients don't change at all.
You still have knowledge of the root cause of the issue, because you nest those exceptions
this way:
} catch (SQLException sqle) {
LOGGER.error(sqle);
throw new PersistenceException(sqle);
} finally {
DBConnection.close(conn);
}
return mfrs;
So you haven't lost the original problem, but you haven't violated tiers either.
Tier 5: Persistence
The RDBMS or XML file or fixed length field text file or serialization to the file system
or whatever stores your data.
With the sole exception of Tier 4 (DAO), there is not a reason in the world that any other tier
or the model or any other thing in the whole world of your app should know how your data is stored.
There are a lot of other patterns that come into play at the various stages here. But this
is the basic structure.
Create your models and transfer objects without regard to any of these tiers. They must
be completely independent of any knowledge of how these tiers are implemented.
So we see quickly that a given tier can
1) know about the tier immediately below it, but
2) it must not know about any tiers below that tier, and
3) It must not know anything about tiers above it.
Period. Ever.
This is so important.
A model must know have code that mentions Struts in it. Period.
An EJB must not know that it is talking to a DAO that is talking to Oracle 9i. Ever.
If it does, you're doing it wrong and you or your users will suffer.
You can pay now, or you can pay later. It's really just easier to pop up a dialog when
the problem happens. That's why you'd program that way. But nothing's free, and eventually
you will pay. And in programming as in life, when you pay on the installment plan, there's
always interest, and it's always more expensive.
Don't write code like this:
public Product(boolean webProduct) { ... }
to indicate whether your model is a product that will be shown on the web or not. These are
not the only two choices (web or not web). What if it's web or swing or a C++ app at the
other end of a web service in which case its XML which means you do't know if it's a "web product"
or not or in a midlet on a cell phone. Boolean doesn't cover all of
those choices. But a model shouldn't know about any of the implementations of any of the tiers
in which it participates.
If you code that way, why not start writing Struts-specific stuff in your DAOs? What if
you change to JavaServer Faces? Still digging in your heels after it's an official
requirement for implementing the Java EE 5 spec?
I see examples of this surprisingly often. I feel weird writing this even because these
things have been said so many times, by people smarter than myself. But I am writing it
because it hasn't seemed to soak in quite yet.
If you violate these tier boundaries, you are guaranteed to have problems. It might take
six months or a year for them to come up, but they will come up.
IBM for some reason assumed that its driver would always be running on a box with a load
of GUI controls. That assumption is totally unfounded and caused me to go on a wild goose
chase that wasted a day for me.
To have a database driver pop up a GUI dialog box is a tier boundary violation, and is
bad writing.
Be strict with yourself about this sort of thing. It sounds a little far-fetched maybe when starting a
project that different tiers might change. But they really are likely to. Systems get interconnected
by business people in ways that programmers never imagine. Companies get bought. It is very
important not to paint yourself into a corner.
When you want to slip business logic into your delegate because it's quick and easy, you've got
to resist that urge.