Welcome to the Enterprise Java Technologies Tech Tips for May 13, 2003. Here you'll get tips on using enterprise Java technologies and APIs, such as those in Java 2 Platform, Enterprise Edition (J2EE).
This issue covers:
Java Logging with J2EE
HTTP Request Forwarding, Redirecting, and Including
These tips were developed using Java 2 SDK, Standard Edition, v 1.4 and Java 2 SDK, Enterprise Edition, v 1.3.1 (Reference Implementation).
This issue of the Tech Tips is written by Mark Johnson, president of elucify technical communications, and co-author of Designing Enterprise Applications with the Java 2, Enterprise Edition, 2nd Edition. Mark Johnson runs an open forum for discussion of the tips.
You can download the sample code. The context root for the application is ttmay2003. The index.html welcome file indicates how to use the sample code. Any use of this code and/or information below is subject to the license terms.
JAVA LOGGING WITH J2EE
Many applications, especially enterprise applications, require logging facilities. Application logs help service engineers troubleshoot problems in the field, and can provide an audit trail for security analysis.
Originally, the Java platform contained few resources for application logging. Developers and administrators made do with println statements in log files, invented homegrown solutions, or turned to one of the many excellent (but mutually incompatible) logging products produced by commercial and open-source developers. Java 2 version 1.4 changed that with the introduction of the java.util.logging package.
This tip briefly explains the basics of Java logging, and provides sample code that sends application log messages to a standalone client by way of a Java Messaging Service (JMS) Topic. If you're unfamiliar with JMS, see the tip "Using JMS Queues" in the March 11, 2003 issue, and "Publish/Subscribe Messaging With JMS Topics" in the April 15, 2003 issue.
The java.util.logging Package
The Java logging package was initially created as a Java Specification Request (JSR-047), and is now a part of Java 2, Standard Edition (J2SE). Its requirements included the following features:
- Minimal runtime performance impact
- Runtime enabling/disabling of logging
- Fine-grained control
- Runtime logging service registration
- Interoperation with existing logging services, such as system logs and legacy logging schemes
- Display of high-priority messages to users when appropriate
- Ability to handle internationalized logging messages
- Ability to log objects, instead of logging only strings
Because the logging package is part of J2SE, it is available in the other Java platforms, including J2EE. Specifically, Java logging can be used from within application clients, servlets, JSP pages, enterprise beans, and connectors.
Logging Classes and Interfaces
The primary class in the logging facility is Logger. A Logger represents a sort of channel through which logging messages can be sent. Usually, each class has its own Logger.
A Logger is configured with a Level, this is a class that indicates the severity of the problem being reported and/or the level of detail required for that individual class. Each message sent to the Logger has an associated Level. The Logger only reports errors whose Level is the same or higher than the Logger's.
The highest logging Level is SEVERE, which indicates a severe problem -- often a fatal error. Other Level values (in decreasing order) are WARNING and INFO, for recoverable and informational messages, respectively. The CONFIG level indicates that a configuration event (such as reading a properties file) has occurred. A programmer can use Level values FINE, FINER, and FINEST to report successively more fine-grained logging messages. Service engineers can use these Level values to hide extraneous detail, or increase the level of logging detail, on a class-by-class basis. The default Level for a Logger is INFO.
A LogRecord is an object that represents a message that should be written to a log. It contains a variety of information, including the message to be printed, the name of the Logger to which it was originally sent, the Level and creation time and date of the message being sent, and the thread id of the caller.
An abstract class called Handler represents a class that knows how to represent a LogRecord in a way that is useful. The logging package contains some Handlers (such as ConsoleHandler, FileHandler, StreamHandler, and SocketHandler) that report LogRecords to various types of destinations. Each Logger can have multiple Handlers. Each Handler also has a Level, and discards any LogRecord whose Level is below its own. So, for example, a single Logger might have two Handlers: one that writes to a log file on a disk, and another that sends Logging messages to a system administrator's pager. The disk log Handler could be configured with the Level FINEST, and so it would write all log messages to disk. By contrast, the pager log Handler might report only SEVERE log messages.
The Formatter class (and any subclasses it defines) converts a LogRecord to a String for printing. For example, the XMLFormatter subclass of Formatter writes a LogRecord as well-formed XML. Developers can write custom Formatter classes.
Finally, the Filter interface allows a programmer to write a method (isLoggable), which allows programmatic control of which LogMessages are discarded or printed.
Sample Code
The sample code for this tip demonstrates how to set up a Logger, and use it to send XML-formatted log messages from the Web tier, over JMS, to a standalone JMS client.
The sample code for this tip comprises a servlet and a standalone client. The servlet is called LogDemoServlet. The standalone client is called LoggingReceiver. The standalone client listens for messages being published to a JMS topic, and prints any TextMessages it receives. The sample application contains an HTML form for a user to post data to the LogDemoServlet.
Notice that the form prompts the user to download a JAR file, and then run that file as a standalone client. The user needs to do this before posting data through the form.
The servlet responds by publishing an XML-formatted message to a Topic. If the LoggingReceiver is running, the user sees the XML-formatted messages printed on the screen.
For example:
<?xml version="1.0" encoding="MacRoman" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2003-05-05T23:51:20</date>
<millis>1052200280257</millis>
<sequence>0</sequence>
<logger>com.elucify.tips.may2003.LogDemoServlet</logger>
<level>INFO</level>
<class>com.elucify.tips.may2003.LogDemoServlet</class>
<method>doPost</method>
<thread>10</thread>
<message>quest= { 'Johnny' }
FavoriteColor= { 'Violet' }
name= { 'Johnny' }
</message>
</record>
|
The key component in the sample code is the servlet LogDemoServlet. This servlet simply formats its POST parameters as a string, and logs that string to the Logger each time the servlet is called. The beginning of the method doPost sets up the Logger:
// Handle post request
public void doPost(HttpServletRequest req,
HttpServletResponse res)
throws IOException, ServletException {
res.setContentType("text/html");
PrintWriter pw = res.getWriter();
// Get POST parameters and write them as
// "variable=value" to a ByteArrayOutputStream
String postParams = getPostParams(req);
ByteArrayOutputStream bos =
new ByteArrayOutputStream();
Logger logger = Logger.getLogger(
this.getClass().getName());
|
The servlet sets the content type, gets a Writer, and formats the POST parameters. It then creates a Logger by calling static method Logger.getLogger(this.getClass().getName()). This method returns the existing Logger of that name, or creates one lazily if it did not already exist. The code uses the fully-specified class name of the servlet class. This is a common convention for ensuring that the names of the various Loggers in a system do not collide.
By default, the Logger writes any messages it creates to the standard output of whatever code it is running. For example, Web-tier messages typically go to the Web server log file.
The second half of the doPost method adds a new Handler to the Logger. Each time a message is logged to the Logger, the Logger sends the message to both Handlers (assuming the message's Log level is high enough). Here is the second half of the doPost method:
// Format logging messages as XML,
// store in byte array
StreamHandler sh = new StreamHandler(
bos, new XMLFormatter());
logger.addHandler(sh);
logger.log(Level.INFO, postParams);
sh.flush();
// Send contents of buffer as JMS message
// to listeners
publish(bos.toString());
pw.println(
"Logging messages sent to subscribers");
}
|
A StreamHandler is a handler that uses a Formatter to format a LogRecord as a string, and writes it to an OutputStream. In this case, the OutputStream is a ByteArrayOutputStream, so the results of the logging are written into memory. This method also uses an XMLFormatter so that the LogRecords are written as XML. The call to addHandler adds the new StreamHandler to the Logger, and then sends a log message containing the formatted POST parameters to the Logger. The Logger sends the message to all of its Handlers, including the new StreamHandler. The call to flush() ensures that all written bytes are flushed to the ByteArrayOutputStream. Finally, the ByteArrayOutputStream is converted to an XML string. The string is published to a JMS Topic by the publish method. A description of how to send messages to JMS Topics (using this same publish method) appears in the tip "Publish/Subscribe Messaging With JMS Topics" in the April 15, 2003 issue.
To run the sample code for this tip see "Running the Sample Code" below.
HTTP REQUEST FORWARDING, REDIRECTING, AND INCLUDING
One of the things that makes Web applications so powerful is their ability to link between and aggregate information resources. The J2EE platform offers three related, yet distinct, ways for a Web component at a specific URL to use data from other URLs to create a response. This tip examines this capability by exploring request forwarding, URL redirection, and inclusion using the Java Servlet API.
Request Forwarding
Request forwarding allows a component to send a request to one URL in an application and have it processed by a component at a different URL in the same application. This technique is commonly used in Web-tier controller servlets which examine data in a Web request and direct the request to the appropriate component for processing.
A servlet can forward an HTTP request it has received using the method javax.servlet.RequestDispatcher.forward. The component that receives the forwarded request can process the request and produce a response, or it can forward the request to yet another component. The ServletRequest and ServletResponse objects from the original request are passed to the component that is the target of the forward. This gives the target component access to the entire request context. Requests may be forwarded only to components within the same application context root, never between applications.
The sample code for this tip involves an HTML form in the index.html file of the sample application. A user selects a source file name from a select list and clicks the submit button (which is labeled "Go").
This corresponds to item 1 in the Tip 2 form.
The form is POSTed to the DispatchDemoServlet, which handles the processing of all three examples for this tip. Request forwarding is handled by the doForward method of the servlet, which appears below.
protected void doForward(HttpServletRequest req,
HttpServletResponse res)
throws IOException, ServletException {
String name = req.getParameter("name");
// Look up the site by name
String url = (String)_p.get(name);
if (url == null) {
url = "errorPage.html";
}
// Get the request dispatcher -- request will be
// dispatched to this URL.
RequestDispatcher rd =
req.getRequestDispatcher(url);
// Forward to requested URL
rd.forward(req, res);
}
|
The POST parameter 'name' indicates the symbolic name of the file requested by the user. The method looks up the URL for that file in a hash table. (The hash table was loaded from a properties file in the application archive.) The method then gets a RequestDispatcher object from the HttpServletRequest object. The RequestDispatcher is implemented by the Web container. The invocation rd.forward instructs the Web container to call the Web component at the given URL. The result of the request is whatever content is returned by the component at that URL.
URL Redirection
URL redirection is similar to request forwarding, but with some important differences. A Web component can redirect a request to any URL, not just URLs in the same application context. But the contents of the original request, such as POST parameters, are lost. This is because the server is not involved in the process of redirecting the request, as is the case with request forwarding. URL redirection works by using the Refresh feature of the HTTP META header. Essentially, the server returns a META tag that tells the browser to go somewhere else entirely. At that point, any data POSTed to the original URL is lost.
URL redirection can be accomplished by directly manipulating HTTP headers, but the preferred way is to use the method javax.servlet.ServletResponse.sendRedirect. This method's single argument is the destination URL of the redirect.
The sample code for URL redirection uses the second form in index.html file for the sample application. A user selects from several URLs on the java.sun.com site, and clicks Go. This corresponds to item 2 in the Tip 2 form.
The form posts the selected form name to DispatchDemoServlet, which calls its method doRedirect, shown below.
protected void doRedirect(HttpServletRequest req,
HttpServletResponse res)
throws IOException, ServletException {
String name = req.getParameter("name");
// Look up the site by name
String url = (String)_p.get(name);
if (url == null) {
url = "errorPage.html";
}
// Redirect request
res.sendRedirect(url);
}
|
As is the case with the doForward method that does request forwarding, the doRedirect method looks up the URL for the redirect using a symbolic name selected from the form by the user. The doRedirect method then calls the HttpServletResponse's sendRedirect method, which structures and sets an HTTP header in the request and immediately returns. The browser receives an empty response, but because of the META header, promptly issues another request for the new URL.
Although this servlet produces an empty response, it is good practice to include a message explaining that the request is being redirected. It's also a good idea to provide a link to the redirection target in case the redirect fails. Older browsers might not implement the Refresh function, or the function might be turned off for security reasons. The link was left out of the example servlet to simplify the code.
Inclusion
So far, you've seen two ways of producing an alternate response to a request: request forwarding and URL redirection. By contrast, inclusion allows a Web component to aggregate data from several other Web components, and use the aggregated data to create a response. This technique is commonly used in template processors. Here a structured template (often a JSP page) is used to control the layout of a response. The content of each page area in the template comes from different URLs, and is assembled into a single page. This technique can provides a consistent look and feel to an application.
Content from multiple Web components can be included in a single response. To include the data from another URL in a response, get a RequestDispatcher for that URL, and call the RequestDispatcher's include method.
The sample code for inclusion uses a set of checkboxes, each of which indicates an HTML file in the application archive. Each HTML file corresponds to a color to be included in the output. A user selects a number of files by checking the boxes, and clicks Go. This corresponds to item 3 in the Tip 2 form.
The DispatchDemoServlet uses its doInclude method, shown below, to process the request.
// Given a list of checked files, include each one of
// them in the output to form a compound document
protected void doInclude(HttpServletRequest req,
HttpServletResponse res)
throws IOException, ServletException {
String name = req.getParameter("name");
// Get the request dispatcher -- request will be
// dispatched to this URL.
RequestDispatcher rd;
if ((rd = req.getRequestDispatcher("/header.html"))
!= null) {
rd.include(req, res);
}
String[] names = req.getParameterValues("color");
String url;
for (int i = 0; names != null &&
i < names.length; i++) {
if ((url = (String)_p.get(names[i])) == null) {
continue;
}
if ((rd = req.getRequestDispatcher(url))
!= null) {
rd.include(req, res);
}
}
if ((rd = req.getRequestDispatcher("/footer.html"))
!= null) {
rd.include(req, res);
}
}
}
|
Notice how the doInclude method calls include several times. Each time it calls include, doInclude must get a RequestDispatcher for the appropriate URL. The method first includes the file /header.html. It then gets the names of the checkboxes in the list, and looks up the corresponding URLs in the hash table. For each URL it finds, doInclude acquires a RequestDispatcher for that URL, and includes the content from that URL in the output. The call to the include method writes the output for that URL to the HttpServletResponse object. Finally, doInclude writes the contents of the URL /footer.html (relative to the context root) to the output.
The selected files and their colors are then displayed to the user:
To run the sample code for this tip see "Running the Sample Code" below.
RUNNING THE SAMPLE CODE
Download the sample archive for these tips. The application's context root is ttmay2003. The downloaded EAR file also contains the complete source code for the sample.
You can deploy the application archive (ttmay2003.ear) in the J2EE Reference Implementation using the deploytool program:
$J2EE_HOME/deploytool -deploy ttmay2003.ear localhost
Replace localhost with the name of the host on which the server is installed. For a standard installation on a single machine, the hostname typically (and literally) is localhost. You can access the application at http://localhost:8000/ttmay2003.
The sample code for the first tip requires you to download a JAR file, and then run that file as a standalone client. Click the link in Step 1 of the example, and save the JAR file. Then issue the following command on the command line:
java com.elucify.tips.may2003.LoggingReceiver
Be sure to either put the JAR file in your CLASSPATH, or add it using the java command's "-classpath" option.
Also, before running the sample code for the first tip, you need to give the sample code permission to set logging handlers. To do this, place the following line in the final "grant" block of the file $J2EE_HOME/lib/security.policy (do this before the closing curly brace):
permission java.util.logging.LoggingPermission "control", "";
For a J2EE-compliant implementation other than the Reference Implementation, use your J2EE product's deployment tools to deploy the application on your platform.
See the index.html welcome file for instructions on running the application.
IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html
Comments? Send your feedback on the Enterprise Java Technologies
Tech Tips to: http://wwws.sun.com/contact/developer_feedback.jsp
Subscribe to other Java developer Tech Tips:
- Core Java Technologies Tech Tips. Get tips on using core
Java technologies and APIs, such as those in the Java 2
Platform, Standard Edition (J2SE).
- Wireless Developer Tech Tips. Get tips on using wireless
Java technologies and APIs, such as those in the Java 2
Platform, Micro Edition (J2ME).
To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page,
choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".
ARCHIVES: You'll find the Enterprise Java Technologies Tech Tips archives at:
http://developer.java.sun.com/developer/EJTechTips/
Copyright 2003 Sun Microsystems, Inc. All rights reserved. 4150 Network Circle, Santa Clara, CA 95054 USA.
This document is protected by copyright.
Trademark Information: http://www.sun.com/suntrademarks/
Java, J2EE, J2SE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.
|