Enterprise Java Technologies Tech Tips Tips, Techniques, and Sample Code Welcome to the Enterprise Java Technologies Tech Tips for June 26, 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: * Listing Web Archive Resources * Servlet Life Cycle Listeners 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 (http://www.elucify.com/), and co-author of Designing Enterprise Applications with the Java 2, Enterprise Edition, 2nd Edition (http://java.sun.com/blueprints/guidelines/ designing_enterprise_applications_2e/). Mark Johnson runs an open forum for discussion of the tips at http://groups.yahoo.com/group/techtipsarchive/. You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/EJTechTips/2003/tt0626.html See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms. You can download the sample archive for these tips at http://java.sun.com/jdc/EJTechTips/download/ttjun2003.ear. The context root for the application is ttjun2003. 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 at http://developer.java.sun.com/berkeley_license.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - LISTING WEB ARCHIVE RESOURCES Occasionally, you may want to write a Web application component that produces content, or changes its behavior, based on the contents of the Web archive. For example, imagine you ship an application to several different customers, each of whom requires some custom functionality and content, and all of whom need occasional updates and extensions. In addition, the application might enable and disable various features depending on the absence or presence (and contents) of various license files. Web components in this type of application could decide how to behave based on the contents of the WAR file. The method javax.servlet.ServletContext.getResourcePaths allows a Web component to query the container about the resources contained in the Web archive. The method returns a java.util.Set of String objects that provide a listing of all of the resources in the Web archive in a particular directory. The single argument is a String that gives a partial path which all of the returned paths must match. The argument must itself start with a forward slash ("/"). All of the strings returned by this method start with a forward slash. Resources corresponding to WAR file directories also end with a slash. Resources corresponding to files do not end with a slash. If the path argument is "/", the method behavior defaults to listing the contents of the root directory of the archive. In some cases (as you will see in the example below), the method reports Web resources that exist in the archive but are not accessible by the component. The Sample Code The sample code for this tip, ListResourcesServlet, allows a user to browse the contents of the tip sample code archive. The servlet receives a get request from the browser. The request contains a single parameter called "path" that indicates the path within the archive to list. Each item in the requested directory is presented to the user as a hyperlink to the named resource. When the user clicks on a hyperlink to a WAR directory, the doGet method lists the contents of that directory. Otherwise, the named resource is a file, which the servlet tries to serve. The doGet method of the servlet is simple -- all it does is distinguish between directory and file resources, based on whether or not the path ends in "/". public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { // The argument "path" is the path of the thing // we want to display. String path = req.getParameter("path"); if (path == null) { throw new ServletException( "ListResourcesServlet: parameter 'path'" + " required"); } // If its name ends in "/", list the resources // in that directory. // Otherwise serve the indicated file. if (path.charAt(path.length()-1) == '/') { listDirectory(res, path); } else { serveFile(req, res, path); } } The serveFile method is straightforward: it simply forwards the request to the path to the resource. The Web container then either serves the resource, or generates an error message if the resource is inaccessible. protected void serveFile(ServletRequest req, ServletResponse res, String path) throws IOException, ServletException { ServletContext ctx = _config.getServletContext(); RequestDispatcher rd = ctx.getRequestDispatcher(path); rd.forward(req, res); } The listDirectory method uses the getResourcePaths method to list the contents of a WAR directory. The method first sets the content type and gets a PrintWriter to the servlet output. Then it prints a line for the ".." directory, which corresponds to the directory above the requested directory. // List the resources in the directory rooted // in "path" protected void listDirectory(ServletResponse res, String path) throws IOException, ServletException { // Get output writer res.setContentType("text/html"); PrintWriter out = res.getWriter(); out.println( "

Listing of resources in directory " + path + "

"); // If the path variable is empty, list "/". if (path == null) { path = "/"; } // Create a link to ".." listDirectoryLine(out, "..", dotdot(path)); The dotdot method creates the name of the directory above the path. It does this by simply eliminating the last directory name (delimited by slashes) from the path. The listDirectoryLine method prints a string (its second argument) as a hyperlink to a path (its third argument) to the servlet output writer. The listDirectory method then uses the getResourcePaths method to list the contents of the requested path. // List directory contents as resource links ServletContext ctx = _config.getServletContext(); Set files = ctx.getResourcePaths(path); Iterator i = files.iterator(); while (i.hasNext()) { String url = (String)i.next(); listDirectoryLine(out, url, url); } The ServletContext object for a servlet is accessed using the getServletContext method of the ServletConfig object. (When a servlet container initializes a new servlet, it passes a ServletConfig object to the servlet's init method. A reference to the ServletConfig can be stored in a field for use by subsequent servlet method calls.) The method then calls the getResourcePaths method to get all resources in the archive matching the given path. Finally, the method iterates over the result set, printing a line for each resource. Sample Code Results Follow the instructions in the section "Running the Sample Code" to download and deploy the sample archive, and to access the application. Click the link for Tip 1 on the displayed page to browse the contents of the sample WAR file. This runs the servlet, with the path equal to "/", and produces the following output (all but the first line are hyperlinks to the corresponding resources). Listing of resources in directory / .. /src/ /original.war /META-INF/ /WEB-INF/ /index.html /htmlsrc/ Notice that all but two of the resources ("/original.war" and "/index.html") end in a slash. Remember that the end slash indicates a directory. The entries that don't end in a slash are files. If you click on the file "index.html", the servlet serves the index.html file (which is the welcome file for the application). By comparison, clicking on the entry "/WEB-INF/" produces the following output: Listing of resources in directory /WEB-INF/ .. /WEB-INF/web.xml /WEB-INF/classes/ Clicking on the link for /WEB-INF/web.xml produces the following unexpected result: J2EE SDK/1.3.1 - HTTP Status 404 - /WEB-INF/web.xml type Status report message /WEB-INF/web.xml description The requested resource (/WEB-INF/web.xml) is not available. If you look in the archive, you will see that the /WEB-INF/web.xml file does indeed exist. So why does the server claim that the file is not available? The answer is that all files under /WEB-INF (and /META-INF) are protected from being served to Web clients. So, although the getResourcePaths method reports all paths, not all paths in a Web archive are accessible to Web components. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SERVLET LIFE CYCLE LISTENERS The Java Servlet 2.3 specification (http://jcp.org/aboutJava/communityprocess/first/jsr053/index.html) provides mechanisms for notifying application code about events in the Web application life cycle. Life cycle events include creation and destruction of a servlet context or an HTTP session, and creation, modification, or removal of servlet context attributes and HTTP session attributes. Furthermore, any object may be notified when it is bound to or unbound from an HTTP session attribute. Application life cycle listeners are often used to provide consistent, managed access to other resources. For example, consider a set of servlets within the same Web application that share a database connection on a single server. To improve performance, some servlets save a database connection as a servlet context attribute, and reuse it across multiple servlet service invocations for multiple clients. When the application is shut down, the database connection should be closed. To manage the database connection, an application developer creates a servlet context event listener. The listener class opens the database and initializes a context attribute with a reference when the application is deployed. The listener class also closes the database when the application is undeployed. To create a life cycle listener, an application developer writes classes that implement listener interfaces for life cycle events, and the deployer configures these classes to be used by a Web application. The class files for the listeners are packaged in the Web archive, and configured using the deployment descriptor web.xml. A listener class must have a public constructor with no arguments. Each Web application can have multiple listeners of each type. The interfaces provided for listening to Web application life cycle events are: - javax.servlet.ServletContextListener. The methods in this interface are called when a new servlet context is created or just before the servlet context is destroyed. - javax.servlet.http.HttpSessionListener. The methods in this interface are called when an HTTP session is created or destroyed. Whether the call to the method occurs before or after the session is invalidated, depends on which specification version (2.3 or 2.4) is being used. It's worthwhile to verify the behavior of the platform implementation you are using if your application behavior depends on this detail. - javax.servlet.ServletContextAttributeListener. The methods in this interface are called when a servlet context attribute is created, modified, or removed from a servlet context. - javax.servlet.http.HttpSessionAttributeListener. The methods in this interface are called when a session attribute is created, modified, or removed from an HTTP session. - javax.servlet.http.HttpSessionBindingListener. The methods in this interface are called when an object is bound or unbound from an HTTP session attribute. Another interface, HttpSessionActivationListener, allows objects bound to session attributes to listen for session passivation or activation. Let's have a brief look at some listening events, and the listeners that watch for them (the HttpSessionActivationListener interface is not covered in this tip). Listening for Servlet Context Creation and Destruction There is a single ServletContext for each Web application, for each Java virtual machine*. Because of this, a ServletContext is typically initialized when a Web application is deployed. The ServletContext is destroyed when a Web application is undeployed. The Web container calls ServletContextListener.contextInitialized when the Web application is ready to process request. The container calls the method contextDestroyed when the application is going to be shut down. Listening for HTTP Session Creation and Destruction An HTTP session is associated with a single application client, and is often created only when requested by the application code. As mentioned earlier, the listener class, HttpSessionListener, has methods that are called at HTTP session and destruction. The class has two methods. The first method, sessionCreated, is called when the Web container creates the session. The second method, sessionDestroyed, is called when a session is destroyed. A session may be destroyed either explicitly by invalidation or by timeout. Listening for Servlet Context Attribute Operations Operations on servlet context attributes can be monitored by implementing java.servlet.ServletContextAttributeListener in a listener class. The Web container calls interface methods attributeAdded, attributeReplaced, and attributeRemoved when servlet context attributes are added, replaced, or removed, respectively. Listening for HTTP Session Attribute Operations A listener class that implements java.servlet.http.HttpSessionAttributeListener is notified when there are changes to HTTP session attributes. The Web container calls interface methods attributeAdded, attributeReplaced, and attributeRemoved when HTTP session attributes are added, replaced, or removed, respectively. Furthermore, if the object being added or removed to the session implements HttpSessionBindingListener, the Web container calls the binding listener's valueBound or valueUnbound method to notify the object of the change. Configuring Listener Classes At deploy time, listener classes are declared using the tag in the deployment descriptor file, web.xml. The Web container is responsible for creating and providing callbacks to instances of the listener classes. An excerpt from this month's sample code shows how to define listeners for an application: TTJun2003WebApp Demonstrate resource listing and life cycle listeners com.elucify.tips.jun2003.LifeCycleServletContextListener com.elucify.tips.jun2003.LifeCycleHttpSessionListener ... Sample Code If you haven't already done so, follow the instructions in the section "Running the Sample Code to download and deploy the sample archive, and access the application. Then go to the section titled "Tip 2: Listener Interfaces" in the display page. You'll see an HTML form that allows you to set, modify, and delete HTTP session attributes (but not servlet context attributes), as well as set the HTTP session timeout value. The sample code defines listeners for three types of events: servlet context creation/destruction, session attribute modification, and session attribute value binding notification (if the corresponding check box is checked). Each method simply prints its name to standard output, to indicate that the callback to the method occurred. The callbacks print feedback to the server process's standard output unit. (In the Reference Implementation, this output is directed to the server log.) For instance, submitting the following information in the Set Attribute portion of the form: Name: cookie-name Value: myCookie prints the following in the server log of the Reference Implementation: LifeCycleServlet: instance created LifeCycleServlet.init: servlet initialized LifeCycleHttpSessionListener: sessionCreated LifeCycleSessionAttributeListener: attributeAdded name = 'cookie-name', value = 'myCookie' LifeCycleServletContextListener is a typical listener class in the sample code. Here is an excerpt from that class: public class LifeCycleServletContextListener implements ServletContextListener { public LifeCycleServletContextListener() { } public void contextInitialized( ServletContextEvent sce) { System.out.println( "LifeCycleServletContextListener: contextInitialized"); } public void contextDestroyed( ServletContextEvent sce) { System.out.println( "LifeCycleServletContextListener: contextDestroyed"); } } You will notice when you deploy the Web archive that the contextInitialized call occurs before any service calls are performed. Use the Web form in the application welcome file to set, update, and remove attributes, both with and without binding notification. Experimenting with the listeners allows you to explore how they behave in response to application life cycle events. The complete source code for the sample listener classes and the corresponding servlet (LifeCycleServlet) are bundled in the sample archive. You can also browse the source in HTML using the link to the code on the displayed page. For another example of a ServletContextListener in action, see the source code for the Java Pet Store Sample Application (http://java.sun.com/blueprints/code/index.html). It uses ServletContextListener to instantiate auxiliary objects when the application is deployed. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - RUNNING THE SAMPLE CODE Download the sample archive for these tips (from http://java.sun.com/jdc/EJTechTips/download/ttjun2003.ear). The application's context root is ttjun2003. The downloaded EAR file also contains the complete source code for the sample. You can deploy the application archive (ttjun2003.ear) in the J2EE Reference Implementation using the deploytool program: $J2EE_HOME/deploytool -deploy ttjun2003.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/ttjun2003. 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 * FEEDBACK Comments? Please give us your feedback on the Enterprise Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=sdn * SUBSCRIBE/UNSUBSCRIBE 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, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), choose the newsletters you want to subscribe to and click "Update". - To unsubscribe, go to the subscriptions page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), uncheck the appropriate checkbox, and click "Update". - To use our one-click unsubscribe facility, see the link at the end of this email: - ARCHIVES You'll find the Enterprise Java Technologies Tech Tips archives at: http://developer.java.sun.com/developer/EJTechTips/index.html - COPYRIGHT Copyright 2003 Sun Microsystems, Inc. All rights reserved. 4150 Network Circle, Santa Clara, California 95054 USA. This document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html Enterprise Java Technologies Tech Tips June 26, 2003 Trademark Information: http://www.sun.com/suntrademarks/ Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. * As used in this document, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.