NOTE: Feedback for this chapter
should go to dbartlett@pobox.com
Enterprise computing is about
collecting and distributing information.
You do this by creating common
repositories (single points of access) to that information, and allowing people
to get at that information in multiple ways. So enterprise computing is creating
and manipulating those common repositories, and providing ways for users to view
and manipulate the information in those repositories.
In this chapter you’ll see that
there are a number of different ways to achieve this goal.
Opening phrase
Introductory paragraphs
[[ Dave: you mentioned an over-arching
example that would be used throughout the chapter. I would recommend the
“Community Information System” (CIS?) that we worked with for the
servlets seminar. The goal would be to provide various services that are
valuable for a community (of course it could easily be adapted to other uses,
but I think it would be easy to just keep “community” in mind for
the design problem). For example:
*A possible additional
example (for EJB, perhaps) is an anti-spamming system to prevent spam harvesters
– either you have a password, or it actually emails the information back
to you, after verifying your email address shows that you’re a community
member.
It has been estimated that half of all
software development involves client/server operations. A great promise of Java
has been the ability to build platform-independent client/server database
applications. In Java 1.1 this has come to fruition with
Java
DataBase Connectivity (JDBC).
One of the major problems with databases
has been the feature wars between the database companies. There is a
“standard” database language,
Structured Query Language
(SQL-92), but usually you must know which database vendor you’re working
with despite the standard. JDBC is designed to be platform-independent, so you
don’t need to worry about the database you’re using while
you’re programming. However, it’s still possible to make
vendor-specific calls from JDBC so you aren’t restricted from doing what
you must.
JDBC, like many of the APIs in Java, is
designed for simplicity. The method calls you make correspond to the logical
operations you’d think of doing when gathering data from a database:
connect to the database, create a statement and execute the query, and look at
the result set.
To allow this platform independence, JDBC
provides a driver manager that dynamically maintains all the driver
objects that your database queries will need. So if you have three different
kinds of vendor databases to connect to, you’ll need three different
driver objects. The driver objects register themselves with the driver manager
at the time of loading, and you can force the loading using
Class.forName( ).
All this
information is combined into one string, the “database URL.” For
example, to connect through the ODBC subprotocol to a database identified as
“people,” the database URL could be:
String dbUrl = "jdbc:odbc:people";
If you’re connecting across a
network, the database URL will also contain the information identifying the
remote machine.
When you’re ready to connect to the
database, you call the static method
DriverManager.getConnection( ), passing it the database URL, the
user name, and a password to get into the database. You get back a
Connection object that you can then use to query and manipulate the
database.
The following example opens a database of
contact information and looks for a person’s last name as given on the
command line. It selects only the names of people that have email addresses,
then prints out all the ones that match the given last name:
//: c15:Lookup.java // Looks up email addresses in a // local database using JDBC import java.sql.*; public class Lookup { public static void main(String[] args) { String dbUrl = "jdbc:odbc:people"; String user = ""; String password = ""; try { // Load the driver (registers itself) Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver"); Connection c = DriverManager.getConnection( dbUrl, user, password); Statement s = c.createStatement(); // SQL code: ResultSet r = s.executeQuery( "SELECT FIRST, LAST, EMAIL " + "FROM people.csv people " + "WHERE " + "(LAST='" + args[0] + "') " + " AND (EMAIL Is Not Null) " + "ORDER BY FIRST"); while(r.next()) { // Capitalization doesn't matter: System.out.println( r.getString("Last") + ", " + r.getString("fIRST") + ": " + r.getString("EMAIL") ); } s.close(); // Also closes ResultSet } catch(Exception e) { e.printStackTrace(); } } } ///:~
You can see the creation of the database
URL as previously described. In this example, there is no password protection on
the database so the user name and password are empty strings.
Once the connection is made with
DriverManager.getConnection( ), you can use the resulting
Connection object to create a Statement object using the
createStatement( )
method. With the resulting
Statement, you can call
executeQuery( ),
passing in a string containing an SQL-92 standard SQL statement. (You’ll
see shortly how you can generate this statement automatically, so you
don’t have to know much about SQL.)
The executeQuery( ) method
returns a ResultSet
object, which is quite a bit like an iterator: the next( ) method
moves the iterator to the next record in the statement, or returns false
if the end of the result set has been reached. You’ll always get a
ResultSet object back from executeQuery( ) even if a query
results in an empty set (that is, an exception is not thrown). Note that you
must call next( ) once before trying to read any record data. If the
result set is empty, this first call to next( ) will return
false. For each record in the result set, you can select the fields using
(among other approaches) the field name as a string. Also note that the
capitalization of the field name is ignored – it doesn’t matter with
an SQL database. You determine the type you’ll get back by calling
getInt( ),
getString( ),
getFloat( ), etc. At
this point, you’ve got your database data in Java native format and can do
whatever you want with it using ordinary Java
code.
With JDBC, understanding the code is
relatively simple. The confusing part is making it work on your particular
system. The reason this is confusing is that it requires you to figure out how
to get your JDBC driver to load properly, and how to set up a database using
your database administration software.
Of course, this process can vary
radically from machine to machine, but the process I used to make it work under
32-bit Windows might give you clues to help you attack your own
situation.
The program above contains the
statement:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
This implies a directory structure, which
is deceiving. With this particular installation of JDK 1.1, there was no file
called JdbcOdbcDriver.class, so if you looked at this example and went
searching for it you’d be frustrated. Other published examples use a
pseudo name, such as “myDriver.ClassName,” which is less than
helpful. In fact, the load statement above for the jdbc-odbc driver (the only
one that actually comes with JDK 1.1) appears in only a few places in the online
documentation (in particular, a page labeled “JDBC-ODBC Bridge
Driver”). If the load statement above doesn’t work, then the name
might have been changed as part of a Java version change, so you should hunt
through the documentation again.
If the load statement is wrong,
you’ll get an exception at this point. To test whether your driver load
statement is working correctly, comment out the code after the statement and up
to the catch clause; if the program throws no exceptions it means that
the driver is loading properly.
Again, this is specific to 32-bit
Windows; you might need to do some research to figure it out for your own
platform.
First, open the control panel. You might
find two icons that say “ODBC.” You must use the one that says
“32bit ODBC,” since the other one is for backwards compatibility
with 16-bit ODBC software and will produce no results for JDBC. When you open
the “32bit ODBC” icon, you’ll see a tabbed dialog with a
number of tabs, including “User DSN,” “System DSN,”
“File DSN,” etc., in which “DSN” means “Data
Source Name.” It turns out that for the JDBC-ODBC bridge, the only place
where it’s important to set up your database is “System DSN,”
but you’ll also want to test your configuration and create queries, and
for that you’ll also need to set up your database in “File
DSN.” This will allow the Microsoft Query tool (that comes with Microsoft
Office) to find the database. Note that other query tools are also available
from other vendors.
The most interesting database is one that
you’re already using. Standard ODBC supports a number of different file
formats including such venerable workhorses as DBase. However, it also includes
the simple “comma-separated ASCII” format, which virtually every
data tool has the ability to write. In my case, I just took my
“people” database that I’ve been maintaining for years using
various contact-management tools and exported it as a comma-separated ASCII file
(these typically have an extension of .csv). In the “File
DSN” section I chose “Add,” chose the text driver to handle my
comma-separated ASCII file, and then un-checked “use current
directory” to allow me to specify the directory where I exported the data
file.
You’ll notice when you do this that
you don’t actually specify a file, only a directory. That’s because
a database is typically represented as a collection of files under a single
directory (although it could be represented in other forms as well). Each file
usually contains a single table, and the SQL statements can produce results that
are culled from multiple tables in the database (this is called a
join). A database that
contains only a single table (like this one) is usually called a
flat-file
database. Most problems that go beyond the simple storage and retrieval of
data generally require multiple tables that must be related by joins to produce
the desired results, and these are called
relational
databases.
To test the configuration you’ll
need a way to discover whether the database is visible from a program that
queries it. Of course, you can simply run the JDBC program example above up to
and including the statement:
Connection c = DriverManager.getConnection( dbUrl, user, password);
If an exception is thrown, your
configuration was incorrect.
However, it’s useful to get a
query-generation tool involved at this point. I used Microsoft Query that came
with Microsoft Office, but you might prefer something else. The query tool must
know where the database is, and Microsoft Query required that I go to the ODBC
Administrator’s “File DSN” tab and add a new entry there,
again specifying the text driver and the directory where my database lives. You
can name the entry anything you want, but it’s helpful to use the same
name you used in “System DSN.”
Once you’ve done this, you will see
that your database is available when you create a new query using your query
tool.
The query that I created using Microsoft
Query not only showed me that my database was there and in good order, but it
also automatically created the SQL code that I needed to insert into my Java
program. I wanted a query that would search for records that had the last name
that was typed on the command line when starting the Java program. So as a
starting point, I searched for a specific last name, ‘Eckel’. I also
wanted to display only those names that had email addresses associated with
them. The steps I took to create this query were:
The result of this
query will show you whether you’re getting what you want.
Now you can press the SQL button and
without any research on your part, up will pop the correct SQL code, ready for
you to cut and paste. For this query, it looked like this:
SELECT people.FIRST, people.LAST, people.EMAIL FROM people.csv people WHERE (people.LAST='Eckel') AND (people.EMAIL Is Not Null) ORDER BY people.FIRST
With more complicated queries it’s
easy to get things wrong, but with a query tool you can interactively test your
queries and automatically generate the correct code. It’s hard to argue
the case for doing this by hand.
You’ll notice that the code above
looks different from what’s used in the program. That’s because the
query tool uses full qualification for all of the names, even when there’s
only one table involved. (When more than one table is involved, the
qualification prevents collisions between columns from different tables that
have the same names.) Since this query involves only one table, you can
optionally remove the “people” qualifier from most of the names,
like this:
SELECT FIRST, LAST, EMAIL FROM people.csv people WHERE (LAST='Eckel') AND (EMAIL Is Not Null) ORDER BY FIRST
In addition, you don’t want this
program to be hard coded to look for only one name. Instead, it should hunt for
the name given as the command-line argument. Making these changes and turning
the SQL statement into a dynamically-created String
produces:
"SELECT FIRST, LAST, EMAIL " + "FROM people.csv people " + "WHERE " + "(LAST='" + args[0] + "') " + " AND (EMAIL Is Not Null) " + "ORDER BY FIRST");
SQL has another way to insert names into
a query called
stored
procedures, which is used for speed. But for much of your database
experimentation and for your first cut, building your own query strings in Java
is fine.
You can see from this example that by
using the tools currently available – in particular the query-building
tool – database programming with SQL and JDBC can be quite
straightforward.
It’s more useful to leave the
lookup program running all the time and simply switch to it and type in a name
whenever you want to look someone up. The following program creates the lookup
program as an application/applet, and it also adds name completion so the data
will show up without forcing you to type the entire last name:
//: c15:VLookup.java // GUI version of Lookup.java // <applet code=VLookup // width=500 height=200> </applet> import javax.swing.*; import java.awt.*; import java.awt.event.*; import javax.swing.event.*; import java.sql.*; import com.bruceeckel.swing.*; public class VLookup extends JApplet { String dbUrl = "jdbc:odbc:people"; String user = ""; String password = ""; Statement s; JTextField searchFor = new JTextField(20); JLabel completion = new JLabel(" "); JTextArea results = new JTextArea(40, 20); public void init() { searchFor.getDocument().addDocumentListener( new SearchL()); JPanel p = new JPanel(); p.add(new Label("Last name to search for:")); p.add(searchFor); p.add(completion); Container cp = getContentPane(); cp.setLayout(new BorderLayout()); cp.add(p, BorderLayout.NORTH); cp.add(results, BorderLayout.CENTER); try { // Load the driver (registers itself) Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver"); Connection c = DriverManager.getConnection( dbUrl, user, password); s = c.createStatement(); } catch(Exception e) { results.setText(e.getMessage()); } } class SearchL implements DocumentListener { public void changedUpdate(DocumentEvent e){} public void insertUpdate(DocumentEvent e){ textValueChanged(); } public void removeUpdate(DocumentEvent e){ textValueChanged(); } } public void textValueChanged() { ResultSet r; if(searchFor.getText().length() == 0) { completion.setText(""); results.setText(""); return; } try { // Name completion: r = s.executeQuery( "SELECT LAST FROM people.csv people " + "WHERE (LAST Like '" + searchFor.getText() + "%') ORDER BY LAST"); if(r.next()) completion.setText( r.getString("last")); r = s.executeQuery( "SELECT FIRST, LAST, EMAIL " + "FROM people.csv people " + "WHERE (LAST='" + completion.getText() + "') AND (EMAIL Is Not Null) " + "ORDER BY FIRST"); } catch(Exception e) { results.setText( searchFor.getText() + "\n"); results.append(e.getMessage()); return; } results.setText(""); try { while(r.next()) { results.append( r.getString("Last") + ", " + r.getString("fIRST") + ": " + r.getString("EMAIL") + "\n"); } } catch(Exception e) { results.setText(e.getMessage()); } } public static void main(String[] args) { JApplet applet = new VLookup(); JFrame frame = new JFrame("Email lookup"); //#frame.setDefaultCloseOperation(EXIT_ON_CLOSE); frame.addWindowListener(new WClose()); // 1.2 frame.add(applet); frame.setSize(500, 200); applet.init(); applet.start(); frame.setVisible(true); } } ///:~
Much of the database logic is the same,
but you can see that a TextListener is added to listen to the
TextField, so that whenever you type a new character it first tries to do
a name completion by looking up the last name in the database and using the
first one that shows up. (It places it in the completion Label,
and uses that as the lookup text.) This way, as soon as you’ve typed
enough characters for the program to uniquely find the name you’re looking
for, you can stop.
When you browse the online documentation
for JDBC it can seem daunting. In particular, in the
DatabaseMetaData
interface – which is just huge, contrary to most of the interfaces you see
in Java – there are methods such as
dataDefinitionCausesTransactionCommit( ),
getMaxColumnNameLength( ), getMaxStatementLength( ),
storesMixedCaseQuotedIdentifiers( ),
supportsANSI92IntermediateSQL( ),
supportsLimitedOuterJoins( ), and so on. What’s this all
about?
As mentioned earlier, databases have
seemed from their inception to be in a constant state of turmoil, primarily
because the demand for database applications, and thus database tools, is so
great. Only recently has there been any convergence on the common language of
SQL (and there are plenty of other database languages in common use). But even
with an SQL “standard” there are so many variations on that theme
that JDBC must provide the large DatabaseMetaData interface so that your
code can discover the capabilities of the particular “standard” SQL
database that it’s currently connected to. In short, you can write simple,
transportable SQL, but if you want to optimize speed your coding will multiply
tremendously as you investigate the capabilities of a particular vendor’s
database.
This, of course, is not Java’s
fault. The discrepancies between database products are just something that JDBC
tries to help compensate for. But bear in mind that your life will be easier if
you can either write generic queries and not worry too much about performance,
or, if you must tune for performance, know the platform you’re writing for
so you don’t need to write all that investigation code.
There is more JDBC information available
in the electronic documents that come as part of the Java
1.1 distribution from Sun. In addition, you can find
more in the book JDBC Database Access with Java (Hamilton, Cattel, and
Fisher, Addison-Wesley 1997). Other JDBC books are appearing
regularly.
Traditionally, the way to handle such a
problem is to create an HTML
page with a text field and a “submit” button. The user can type
whatever he or she wants into the text field, and it will be submitted to the
server without question. As it submits the data, the Web page also tells the
server what to do with the data by mentioning the
Common
Gateway Interface (CGI) program that the server should run after receiving this
data. This CGI program is typically written in either Perl or C (and sometimes
C++, if the server supports it), and it must handle everything. First it looks
at the data and decides whether it’s in the correct format. If not, the
CGI program must create an HTML page to describe the problem; this page is
handed to the server, which sends it back to the user. The user must then back
up a page and try again. If the data is correct, the CGI program opens the data
file and either adds the email address to the file or discovers that the address
is already in the file. In both cases it must format an appropriate HTML page
for the server to return to the user.
As Java programmers, this seems like an
awkward way for us to solve the problem, and naturally, we’d like to do
the whole thing in Java. First, we’ll use a Java applet to take care of
data validation at the client site, without all that tedious Web traffic and
page formatting. Then let’s skip the Perl CGI script in favor of a Java
application running on the server. In fact, let’s skip the Web server
altogether and simply make our own network connection from the applet to the
Java application on the server!
As you’ll see, there are a number
of issues that make this a more complicated problem than it seems. It would be
ideal to write the applet using Java 1.1 but
that’s hardly practical. At this writing, the number of users running Java
1.1-enabled browsers is small, and although such browsers are now commonly
available, you’ll probably need to take into account that a significant
number of users will be slow to upgrade. So to be on the safe side, the applet
will be programmed using only Java 1.0 code. With this
in mind, there will be no JAR files to combine .class files in the
applet, so the applet should be designed to create as few .class files as
possible to minimize download time.
Introductory examples from the hands-on
Java seminar which you can use or modify
Works with both GET &
POST:
//: c15:ServletsRule.java //# You must install the JSWDK from java.sun.com //# and add servlet.jar to your classpath in //# order to compile this file. import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class ServletsRule extends HttpServlet { int i = 0; // Servlet "persistence" public void service(HttpServletRequest req, HttpServletResponse res) throws IOException { PrintWriter out = res.getWriter(); out.print("<HEAD><TITLE>"); out.print("A server-side strategy"); out.print("</TITLE></HEAD><BODY>"); out.print("<h1>Servlets Rule! " + i++); out.print("</h1></BODY>"); out.close(); } } ///:~
Easy to get HTML form
data:
//: c15:EchoForm.java //# You must install the JSWDK from java.sun.com //# and add servlet.jar to your classpath in //# order to compile this file. // Dumps the name-value pairs of any HTML form import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; public class EchoForm extends HttpServlet { public void service(HttpServletRequest req, HttpServletResponse res) throws IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); out.print("<h1>Your form contained:</h1>"); Enumeration flds = req.getParameterNames(); while(flds.hasMoreElements()) { String field = (String)flds.nextElement(); String value = req.getParameter(field); out.print(field + " = " + value + "<br>"); } out.close(); } } ///:~
One servlet,
thread-per-request:
//: c15:ThreadServlet.java //# You must install the JSWDK from java.sun.com //# and add servlet.jar to your classpath in //# order to compile this file. import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class ThreadServlet extends HttpServlet { int i; public void service(HttpServletRequest req, HttpServletResponse res) throws IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); synchronized(this) { try { Thread.currentThread().sleep(5000); } catch(InterruptedException e) {} } out.print("<h1>Finished " + i++ + "</h1>"); out.close(); } } ///:~
Here’s an extremely simple JSP
example that uses a standard Java library call to get the current time in
milliseconds, which is then divided by 1000 to produce the time in seconds.
Since a JSP expression (the <%= ) is used, the result of the
calculation is coerced into a String so it can be printed to the
out object (which puts it int the web page):
//:! c15:jsp:ShowSeconds.jsp <html><body> <H1>The time in seconds is: <%= System.currentTimeMillis()/1000 %></H1> </body></html> ///:~
//:! c15:jsp:Hello.jsp <%-- This JSP comment will not appear in the generated html --%> <%-- This is a JSP directive: --%> <%@ page import="java.util.*" %> <%-- These are declarations: --%> <%! long loadTime= System.currentTimeMillis(); %> <%! Date loadDate = new Date(); %> <%! int hitCount = 0; %> <html><body> <!-- This HTML comment will appear as a comment on the generated html page --> <%-- The following comment has the result of a JSP expression inserted in the generated html; the '=' indicates a JSP expression --%> <!-- This page was loaded at <%= loadDate %> --> <H1>Hello, world! It's <%= new Date() %></H1> <H2>Here's an object: <%= new Object() %></H2> <H2>This page has been up <%= (System.currentTimeMillis()-loadTime)/1000 %> seconds</H2> <H3>Page has been accessed <%= ++hitCount %> times since <%= loadDate %></H3> <%-- A "scriptlet" which writes to the server console. Note that a ';' is required: --%> <% System.out.println("Goodbye"); %> </body></html> ///:~
You cannot nest JSP comments
(‘<%--’ and ‘--%>’) inside of any
other JSP directives, but you can use comments outside of JSP directives to
prevent them from being compiled.
//:! c15:jsp:PageContext.jsp <%--Viewing the attributes in the pageContext--%> <%-- Note that you can include any amount of code inside the scriptlet tags --%> <%@ page import="java.util.*" %> <html><body> <% Enumeration e = pageContext.getAttributeNamesInScope(1); while(e.hasMoreElements()) { out.println("\t<li>" + e.nextElement() + "</li>"); } %> <H4>End of list</H4> </body></html> ///:~
This example first looks to see where
there are any parameters in the submitted form; if not, it knows that
you’re just calling the JSP for the first time and that it should generate
the form (this is a convenient technique to allow you to put the form generator
and its response in the same source file). Next, it displays the fields and
their values (this second section will produce nothing on the initial call to
the JSP).
//:! c15:jsp:DisplayFormData.jsp <%-- Fetching the data from an HTML form --%> <%-- Also generates the form --%> <%@ page import="java.util.*" %> <html><body> <H1>DisplayFormData</H1><H3> <% Enumeration f = request.getParameterNames(); if(!f.hasMoreElements()) { // No fields %> <form method="POST" action="DisplayFormData.jsp"> <% for(int i = 0; i < 10; i++) { %> Field<%=i%>: <input type="text" size="20" name="Field<%=i%>" value="Value<%=i%>"><br> <% } %> <INPUT TYPE=submit name=submit Value="Submit"> </form> <% } %> <% Enumeration flds = request.getParameterNames(); while(flds.hasMoreElements()) { String field = (String)flds.nextElement(); String value = request.getParameter(field); %> <li><%= field %> = <%= value %></li> <% } %> </H3></body></html> ///:~
//:! c15:jsp:SessionObject.jsp <%--Setting and getting session object values--%> <html><body> <H1>Session id: <%= session.getId() %></H1> <H1>This session was created at <%= session.getCreationTime() %></H1> <H3>MaxInactiveInterval= <%= session.getMaxInactiveInterval() %></H3> <% session.setMaxInactiveInterval(5); %> <H3>MaxInactiveInterval= <%= session.getMaxInactiveInterval() %></H3> <H1>Session value for "My dog" <%= session.getValue("My dog") %></H1> <% session.putValue("My dog", new String("Ralph")); %> <H1>My dog's name is <%= session.getValue("My dog") %></H1> <FORM TYPE=POST ACTION=SessionObject2.jsp> <INPUT TYPE=submit name=submit Value="Submit"> </FORM> </body></html> ///:~
//:! c15:jsp:SessionObject2.jsp <%--The session object carries through--%> <html><body> <H1>Session id: <%= session.getId() %></H1> <H1>Session value for "My dog" <%= session.getValue("My dog") %></H1> <% session.invalidate(); %> </body></html> ///:~
//:! c15:jsp:PlayWithCookies.jsp <%--This program has different behaviors under different browsers! --%> <html><body> <H1>Session id: <%= session.getId() %></H1> <% Cookie[] cookies = request.getCookies(); for(int i = 0; i < cookies.length; i++) { %> Cookie name: <%= cookies[i].getName() %> <br> value: <%= cookies[i].getValue() %><br> Max age in seconds: <%= cookies[i].getMaxAge() %><br> <% cookies[i].setMaxAge(3); %> Max age in seconds: <%= cookies[i].getMaxAge() %><br> <% response.addCookie(cookies[i]); %> <% } %> <%-- <% response.addCookie( new Cookie("Bob", "Car salesman")); %> --%> </body></html> ///:~
//:! c15:RequestDispatcher1.jsp <%--Using the request dispatcher forward() to pass control to another servlet --%> <html><body><H1>In RequestDispatcher 1</H1> <% response.setContentType("text/html"); out.println( "Printing to response object from RD 1"); RequestDispatcher rd = application.getRequestDispatcher( "/jsp/RequestDispatcher2.jsp"); rd.forward(request, response); out.println("<H1>Hello</H1>"); %> </body></html> ///:~
//:! c15:RequestDispatcher2.jsp <%--Recieves a dispatched request --%> <html><body> <% response.setContentType("text/html"); out.println( "Printing to response object from RD 2); %> <H1>In RequestDispatcher 2</H1> </body></html> ///:~
//:! c15:RequestDispatcher3.jsp <%--Using a RequestDispatcher.include() --%> <html><body> <H1>In RequestDispatcher 3</H1> <% response.setContentType("text/html"); out.println( "Printing to response object from RD 3"); out.println("<H1>In RD 3</H1>"); RequestDispatcher rd = application.getRequestDispatcher( "/jsp/RequestDispatcher2.jsp"); rd.include(request, response); out.println("<H1>Out RD 3</H1>"); %> <H1>Hello</H1> </body></html> ///:~
The Enterprise JavaBeans (EJB)
specification defines a component model and deployment environment to simplify
the lifecycle of multi-tiered distributed systems. Enterprise JavaBeans (like
all Java 2, Enterprise Edition API’s) is a specification defined by
JavaSoft, endorsed and implemented by many vendors. It not an actual product. An
EJB compliant product, is a product that implements and adheres to the standards
set out in the specification.
The objective of the EJB specification is
to “make it easy to write [server side] applications”. The EJB
specification achieves this objective by defining a set of design patterns for
the server-side code as well as roles, which various developers, administrators,
and system components play in the various phases of the lifecycle of a
distributed application.
Through the use of a set of standard
design patterns and roles - which decouple business logic from system
infrastructure- products which adhere to the EJB specification make it possible
to write enterprise applications without having to understand these low-level
system infrastructure issues such as transactions, caching mechanisms,
multi-threading and security.
The definition of these roles and
components also gives the application developer vendor independence. EJB
Vendors adhere and contribute to the one specification, hence Enterprise Java
Bean components are easily transferable between vendors tools and platforms and
deliver on Java’s promise of “Write Once, Run Anywhere TM ”
philosophy.
Although the EJB specification is a Java
specification, based on and implemented in the Java language, it also defines
interoperability with non-Java Systems. It also defines interoperability with
the CORBA specification.
EJB is probably the most visible of the
many API’s that compromise the Java 2, Enterprise Edition specification.
The classes and interfaces that make up the Enterprise Java Beans API are
defined in the standard extension package javax.ejb. The latest version
of the EJB specification is 1.1 and is available from
http://java.sun.com/products/ejb.
When constructing a multi-tiered system
using RMI,CORBA or any other middleware, System architects and developers are
faced with many challenges such as:
All of these issues
have arisen before we have even scoped the problem that we are trying to solve!
As you can see, a distributed system developer has a lot to think about. The
Enterprise JavaBeans specification acknowledges these issues and that all or
some of them must be overcome when developing a distributed system and defines
how these issues are handled.
Both RMI and CORBA allow for the
development of a distributed application, without getting into a heated debate
of which technology is better (which we can refer to the alien book).
Distributed protocols and API's such as RMI and CORBA, allowed the Enterprise
developer to create their own distributed components. However, the design of
these components were/are left up to the individual developer. Which meant that
components that are created by one development team for a specific server, were
not reusable in another and components that were reusable, usually relied on a
framework that was developed to handle dist tx's, security mechanisms and
caching algorithms in a server dependant manner.
With more and more systems becoming
multi-tiered and various vendors each creating there own solution to the
problems outlined above, it began to lock developers into using one vendors
implementation. As it was very hard to migrate code from one technology to
another.
EJB further refines the architecture by
formally defining how the middle tier is developed. The goal being that the
developer doesn’t have to worry about coding common low-level frameworks
to handle
Distributed transactions, security
mechanisms and caching algorithms. And because all the code that is developed
adheres to a standard, then code in one server, should be able to run in another
EJB Compliant server, without requiring recompiling.
EJB allows for a market of standard
enterprise components that can be sold and redistributed by domain
experts.
EJB corrects the problem by defining a
standard RUNTIME environment, i.e. the EJB Container. The EJB Container handles
and hides the complexities from the bean developer so that the bean developer
can rely on these common services being provided and focus on solving the
business problem, and also avoid being locked into a single vendor’s
architecture.
There is much confusion about the
relationship between the JavaBeans component model and the Enterprise JavaBeans
specification. Whilst both the JavaBeans and Enterprise JavaBeans specifications
share the same objectives in promoting reuse and portability of Java code
between development and deployment tools with the use of standard design
patterns, the motives behind each specification are geared to solve different
problems.
The standards defined in the JavaBeans
component model are designed for creating reusable components that are typically
used in IDE development tools and are commonly, although not exclusively visual
components.
The Enterprise JavaBeans specification
defines a component model for developing server side Java code. An Enterprise
JavaBean can use JavaBeans within its
implementation.
An EJB Server is defined as an
Application Server that contains and runs 1 or more EJB Containers. The
definition of an interface between an EJB Server and EJB Container is currently
not defined in the EJB specification. The specification suggests that both the
Container and Server are the same
vendor.
The EJB specification introduces the
concept of an EJB Container (container). The Container is a runtime environment
that contains components (Enterprise JavaBeans) and provides a set of standard
services to the components. The EJB Containers responsibilities are tightly
defined by the specification to allow for vendor neutrality. The EJB container
is responsible for providing some of the technical benefits of EJB, including
distributed transactions, security, life cycle management of beans, caching,
threading and session management.
The EJB Container achieves these tasks by
acting as the “glue” between the EJB and the client that is using
the Beans services. Because of this level of indirection, the container can
transparently cache beans and propagate transaction and security information
without the client code or bean itself requiring any low-level
code.
Enterprise Java Beans are reusable
components that are developed in accordance with the Design
Patterns highlighted in the EJB
specification. EJBeans can be of 2 different types, Entity Beans and Session
Beans. The EJB 1.0 specification did not require Entity Beans to be implemented,
this has changed in version 1.1, which makes mandatory the implementation of
Entity Beans. The EJB 1.1 specification has also further clarified some
ambiguous issues with Session Beans. It is the responsibility of the EJB
Container to provide caching, session and lifecycle management of all EJBs so
that the Bean Provider can focus on solving the business problem at
hand.
EJB defines 2 design patterns or Bean
types, Session Beans and Entity Beans. Figure x. shows a hierarchy of EJB
Types
<author note>
show simple hierarchy chart of EJB ->
Entity Bean/Session Bean -> BMP/CMP, Stateful/Stateless
</author note>
Session Beans are components that perform
work on behalf of a client and offer these services. Session Beans can be either
Stateful or Stateless and serve only one client. Session Beans represent
operations on persistent data, A Stateful Session Bean maintains it data for a
particular client between method invocations. A Stateless Session bean is
stateless and can be reused by different clients between requests as no state is
maintained within the bean.
Entity Beans are components that
represent data that is persistent e.g. data from a database and behavior of this
data. Persistence in Entity Beans can be managed by the developer, this is known
as “Bean Managed Persistence” or it can be managed by the EJB
Container, possibly with the aid of an Object to Relational Mapping tool. This
approach is known as “Container Managed Persistence” .Multiple
clients can share entity beans, the container is required to handle
multi-threaded issues. Because an Entity bean represents persistent data, the
life of the Entity Bean outlives the
container.
Bean Managed Persistence or BMP is where
the Bean Provider is responsible for implementing all of the logic required to
create a new EJB, update some fields of an Entity Bean, delete an Entity Bean
and find an Entity Bean from persistent store. This usually involves writing
JDBC code to interact with a database. With BMP, the developer is in full
control of how the Entity Bean persistence is managed.
BMP also gives flexibility where a CMP
implementation may not be available e.g., if you wanted to create an EJB that
wrapped some code on an existing mainframe system, you could write your
persistence using CORBA. This would be something that CMP would have a lot of
trouble trying to do!
Container Managed Persistence (CMP) is
where the EJB Container handles the process of persisting Entity Beans on your
behalf. CMP reduces the development time of Entity Beans as the developer can
focus totally on the logic of the component rather than how it is stored and
retrieved.
There are some interesting points that
should be considered when choosing the persistence mechanisms for your EJB
application. If you choose to use CMP, you are relying on a tool to implement
your persistence for you. Whilst this seems like a logical thing to do for
simple beans, it may not have the ability to do complex forms of persistence
such as non-jdbc persistence. Also the lack of standards defined for the Entity
Bean to Relational Database mapping could mean that your Bean is forced to run
in a certain Container or use a certain mapping tool. The table below highlights
some differences and advantages and disadvantages of each persistence
mechanism
Figure x. - Comparison of Persistence
Mechanisms.
Task |
Bean-Managed Persistence |
Container-Managed
Persistence |
Notes |
Required Amount of
Coding |
BMP requires hand coding of the 4
persistence methods, which can mean a lot of coding JDBC calls or other
persistence code |
EJB Container handles all of this on
behalf of the user, and usually creates a set of Relational database tables for
your entity beans. |
|
Performance |
Developer has fine control over
persistence, e.g. update only a certain set of fields or execute a stored
procedure. Developer can easily optimize the database access
calls |
Some CMP tools are very advanced and can
provide optimizations and caching. Some CMP tools are primitive and can only
|
|
Support for different persistence
stores |
BMP will allow the user to change to
SQL/J, when a driver is available rather than waiting for CMP vendor to
implement engine using SQL/J. Also BMP can be implemented using
CORBA |
|
|
Portability/Vendor
Independence |
|
|
|
Things to remember -> Here is an
example of an excellent design aspect of EJB. Because persistence is isolated
from the remote interface, you could change the pm of your bean without harming
any client applications. This means that for early prototype development and EJB
design, one could use CMP and then transfer to bmp and optimize JDBC code with a
DBA or specialist to improve
performance.
The EJB component model allows for the
setting of parameters at deployment time, through the use of a deployment
descriptor. Deployment descriptors allow the modification of environment
variables, transactional attributes and security roles to be defined after the
development of the Bean. This allows the Enterprise JavaBean developer to focus
totally on creating the business logic for the bean.
A deployment descriptor is used to
describe the Enterprise JavaBeans that are contained in an EJB-jar file. The
difference between an EJB-jar file and a normal jar file is the deployment
descriptor. The deployment descriptor must be placed inside the META-INF
directory of the jar file, along with the manifest file. It must also have the
filename EJB-jar.xml
Prior to the EJB 1.1 specification, the
deployment descriptor was a serialized object that was defined in code, compiled
and placed inside the jar file. This made it very difficult to change, because
it required de-serializing the objects, loading them into memory, changing the
attributes, recompiling and deserializing the objects again. This was a tedious
process if the deployer only wanted to change one attribute.
The EJB 1.1 specification changed the
descriptor and totally removed the serialized objects in favor of XML. Moving to
XML has many advantages. The deployment descriptor can be edited and read with a
normal text editor or XML tool, and doesn’t required de-serialization or
recompilation of the deployment object.
Most EJB Tools will contain a utility to
convert from the previous format to the new XML 1.1
format.
Java Naming and Directory Interface
(JNDI) is used in Enterprise JavaBeans as the naming service. It is used to
locate Enterprise JavaBeans and Home objects located on the network. JNDI makes
looking up objects easier than looking up a name in a phone book. JNDI maps very
closely to other naming and directory standards such as LDAP and CORBA
CosNaming. For more details on JNDI
see....
Java Transaction API / Java Transaction
Service. The EJB Container uses JTA/JTS as its transactional API. A Bean
developer can use the JTS to get access to a transaction, but most commonly
transactions are defined at deployment time and the Bean developer can code
their Enterprise JavaBean without having to define transactional boundaries. The
EJB Container is responsible for handling the transaction whether it is local or
distributed. The JTS specification is the Java mapping to the CORBA OTS (Object
Transaction Service). For more details
see....
CORBA is increasingly growing as a
standard in Enterprise development due to its vendor, platform and language
independence. There are many similarities between the services offered by
CORBA, known as the Common Object Services (COS) and the services offered in
Enterprise JavaBeans. Because the Enterprise JavaBeans is a higher level
specification – the wire protocol for EJB is RMI, although RMI can be
implemented on top of a variety of protocols including Java Remote Method
Protocol (JRMP) and CORBA’s Internet Interoperable ORB Protocol (IIOP).
Because of CORBA’s maturity and robustness, it is quite common to see an
EJB Container Provider develop an EJB Container on top of CORBA. Compatibility
with the CORBA specification and IIOP protocol is defined in the EJB
specification and the RMI/IIOP specification. The 1.1 specification quotes
“The Enterprise JavaBeans architecture will be compatible with the
CORBA protocols.”
Implementing an EJB Container on top of
CORBA also allows access to legacy systems and applications that are written in
different languages and again avoids proprietary protocols and vendor lock-in.
The EJB specification defines roles for
different developers in the process of creating an EJB application. It defines
domains that isolate different areas of development. This allows developers of
different technical and domain knowledge to work together effectively. The EJB
Specification defines 6 roles for the development/deployment and management of
an EJB application.
The Enterprise Bean Provider is the
developer who adheres to the EJB design patterns of Entity and Session Beans to
develop beans, which together solve a business problem. Enterprise JavaBeans are
packaged into jar files, The jar file must also contain a Deployment Descriptor
to outline the Enterprise JavaBeans contained within the jar
file.
The role of the Application Assembler is
to assemble applications from a collection of EJB-jar files. The EJB-jar files
may come from a vendor or developed from scratch, for example a company who
specializes in accounting could produce and resell a set of accounting beans to
take care of general accounting tasks. However, these Beans may not perform all
of the tasks required so some extra Enterprise JavaBeans are developed to
interact and customize the Beans into a useful application. The role of the
Application Assembler is to collect all of the Enterprise JavaBeans required and
assemble them into a practical, useful
application.
The Deployer’s role of the to take
the collection of EJB-jar files from the Assembler and/or Bean Provider and
deploy them into an EJB Container. The deployer is responsible for generating
any vendor specific stubs and skeletons that may be required as well as defining
transactional boundaries between Enterprise JavaBeans and security roles. The
Deployer achieves these tasks with the aid of tools that are provided by the EJB
Server/Container Provider.
The current version of the EJB
specification does not clearly define the interface between a Container provider
and a server provider and assumes the same vendor plays these roles. The role of
the Server/Container provider is to provide an EJB Container that is compliant
with the behavior specified in the EJB
specification.
The role of the Systems Administrator is
to oversee the most important goal of the entire system. That it is up and
running. Management and Administration of EJB and Distributed systems is a
crucial yet challenging problem. This is due to the fact that an
“application” is not defined as a process that is running on a
machine. A distributed application can consist of many different components and
services all configured and interacting together
correctly.
<authors note>
I noticed that you guys have some sort of
community theme going, If you send me some details of what we should tailor to
EJB, then I’ll work off that. I’m still undecided whether to show
both a BMP and CMP bean, as support for CMP is fairly fresh right now. Is 1
Entity bean example enough?
Bean Provider - develop the bean, home
and remote interfaces
Bean Provider - create and add entries
into deployment descriptor
Bean Provider - create an EJB-jar
file
Deployer – generate vendor
stubs
Deployer – add assembly and
deployment entries into EJB-jar file
Deployer – add bean into
container
Because the EJB container is responsible
for many complex tasks, it is required that Enterprise Java Bean Components that
are developed adhere to a standard set of patterns. By enforcing many rules, it
also allows for vendor independence between development tools, deployment tools
and EJB Servers/Containers.
Events – Currently there is no
event model specified in EJB. This will be covered in EJB 2.0
Singleton Pattern – Entity and
Session Beans are geared currently towards database and e-commerce applications,
and therefore do not provide suitable patterns for all required scenarios. An
example of this is the Singleton Pattern. A Singleton Pattern is used when the
application wants only 1 specific instance of an object. Because the EJB
Container hides instances from the user, there is no guarantee that the user
will always get the same instance of an object. Here is an example of where
CORBA or RMI could be integrated into an EJB application as these allow the
creation of Singleton objects.
Enterprise Java Beans Home Page -
http://java.sun.com/products/ejb/
specification, downloads
Enterprise Java Beans Special Interest
Group -
http://www.mgm-edv.de/ejbsig/ejbsig.html
Books for further
reading
Summary text
[66]
This section was contributed by Robert Castaneda
(robertc@altavista.net)