DekGenius.com
[ Team LiB ] Previous Section Next Section

16.1 Web Servers

In order to describe where JDO fits into a web server, we start with a brief overview of the web container and how the container handles requests. The application components that handle the requests can use JDO to provide access to persistent information used to service the requests.

There is no standard for all the characteristics of web servers and the services they support, but most implementations support applications written to implement HTTP and HTTPS messages. Since the details of security and secure access to these services are not important to the implementation using JDO, we will use HTTP to describe both HTTP and HTTPS protocols. The use of HTTPS is transparent to the application.

HTTP is a request/response protocol in which a browser sends a request to a server at a specific Internet address and waits for a response from the server. The server parses the request and delegates its handling to the responsible component, based on policy files used to configure the server.

HTTP responses can be static (i.e., their content never changes). Graphics, web-page templates, banners, and other artifacts of web pages are primarily static, and web servers typically cache these items and deliver them to users on request.

Other HTTP responses are dynamic. The response is generated only upon receipt of the request and may depend on current information (time of day, current price of a stock, etc.) or the requester (contents of a shopper's cart, value of a portfolio, etc.). These requests must be handled by a program, which in current web servers might be a script-based component like Common Gateway Interface (CGI) or "PHP: Hypertext Preprocessor," or a programming component.

In a Java-based web server, the programming component that handles the request is either a servlet or a JSP page. Application developers implement programs that adhere to either the servlet or JSP programming contracts to handle requests and generate responses to clients.

SOAP is a remote-object protocol that uses HTTP to transmit requests and receive responses. A web server that supports SOAP provides a layer of processing that interprets SOAP messages, presents them to servlets for processing, and formats the responses for clients.

A server that supports servlet and JSP pages implements a web container that is responsible for managing the lifecycle of servlets and JSP pages, receiving and decoding MIME-type HTTP requests, and formatting MIME-type HTTP responses.

The details of parsing requests and formatting responses will vary based on whether the servlet uses pure HTTP or SOAP, but these details are beyond the scope of this book. Here, we focus on the programming interface to the JDO persistence layer.

To implement a servlet that handles HTTP requests, you extend a base class, HttpServlet, provided by the container implementation. You implement init( ) and destroy( ) and override one or more service methods, typically doGet( ) and/or doPost( ). These methods handle the HTTP-protocol GET and POST requests.

The web container calls init( ) once per servlet instance it creates, and, upon successful completion of the method, it places the servlet into service. This is your application's chance to perform any one-time initialization that is required. You can implement your servlet as a SingleThreadModel, in which multiple requests are dispatched to multiple servlet instances. The SingleThreadModel should be avoided, because the servlet container has to create multiple instances for multiple simultaneous requests.

For SingleThreadModel servlets, if the web container needs to reduce the number of active servlet instances, it selects a servlet instance for destruction and calls destroy( ). This is your last chance to clean up any resources that might have been allocated to this servlet. After this method completes, the servlet will no longer be used and might be garbage-collected by the JVM. Figure 16-1 shows the lifecycle of a servlet.

Figure 16-1. Servlet lifecycle
figs/jdo_1601.gif

16.1.1 Accessing the PersistenceManagerFactory

The servlet programming model is inherently flexible and, theoretically, servlets could dynamically determine which JDO resource contains the information needed to service a request. But most servlets use the same PersistenceManagerFactory instance to service all the user requests, and this resource does not change during the lifetime of the servlet. Therefore, the best time to acquire the PersistenceManagerFactory and save it for future use is during the init( ) call. There are a number of alternate techniques that you can use to initialize the reference to the PersistenceManagerFactory, depending on the support for services provided by the web container.

16.1.1.1 Looking up the PersistenceManagerFactory in JNDI

If the web container is part of a J2EE server, or if it supports the JNDI (Java Naming and Directory Interface) lookup service, you should use the JNDI lookup method and save the result in a servlet field. The container configures the PersistenceManagerFactory at server startup and stores it by name in the JNDI namespace.

To use this facility in a J2EE server, you need to define a resource reference in the deployment descriptor of your web application. This resource reference is part of the servlet specification. The resource-ref element is one of the elements contained in the web-app element (the root of the web-application deployment descriptor):

<resource-ref>
<res-ref-name>jdo/MediaManiaPMF</res-ref-name>
<res-type>javax.jdo.PersistenceManagerFactory</res-type>
<res-auth>Container</res-auth>
</resource-ref>

Your application performs the lookup by using the initial context provided by the container. This initial context is specific to your deployed application, so the name is scoped to your application and you can locate resources that are bound to your application.

The name you look up uses one level of indirection. At deployment time, the deployer makes the association between the name you specify in your application — in this case, java:comp/env/jdo/MediaManiaPMF — and the actual resource that is registered in the server. The details of this deployment step are not standardized, but the indirection allows you to hardcode the resource name and allow the server to bind it to a resource dynamically at deployment time.

This indirection allows multiple applications to use the same hardcoded JNDI name to refer to different resources, as well as multiple applications to use different hardcoded JNDI names to refer to the same resource:

    PersistenceManagerFactory persistenceManagerFactory;
    String pmfName = "java:comp/env/jdo/MediaManiaPMF";
    public void init(ServletConfig config) throws ServletException {
        try {
            super.init(config);
            Context ic = new InitialContext(  );
            persistenceManagerFactory = (PersistenceManagerFactory)
                ic.lookup(pmfName);
        } catch (NamingException ex) {
            throw new ServletException("Unable to locate PMF resource: " +
                pmfName);
        }
    }

The server configures the PersistenceManagerFactory at server startup by a server-specific process. Typically, you configure the URL, username, password, and other properties in an XML-formatted file, and when you look up the resource by name, you get the configured resource. You cannot use any of the set( ) methods of PersistenceManagerFactory to change the properties. If you need to set specific properties, you use the set( ) methods of the individual components (Transaction, Query, or PersistenceManager) after you get the PersistenceManager.

16.1.1.2 Constructing the PersistenceManagerFactory from Properties

If you run your servlet outside a J2EE environment and the web container does not support JNDI, you construct and initialize a PersistenceManagerFactory much as you would in a two-tier environment. Instead of hardcoding the properties of the PersistenceManagerFactory, we recommend that you load a Properties instance identified by a configuration file stored in the WEB-INF directory in the deployed application. This way, you can change the resource without changing any code in your servlet. Simply change the properties file packaged in the war file. This example of initialization is from the servlet named MovieInfo in the com.mediamania.appserver package:

public class MovieInfo extends HttpServlet {
    PersistenceManagerFactory persistenceManagerFactory;
    PersistenceManager pm;

    public void init(  ) throws ServletException {
        try {
            ServletContext ctx = getServletContext(  );
            InputStream in = ctx.getResourceAsStream("WEB-INF/pmf.properties");
            Properties props = new Properties(  );
            props.load(in);
            persistenceManagerFactory = 
                JDOHelper.getPersistenceManagerFactory(props);
        } catch (IOException ex) {
            throw new ServletException("Unable to locate PMF resource.");
        }
    }

The pmf.properties file in this example has the same contents as the properties file used in a two-tier application:

javax.jdo.PersistenceManagerFactoryClass:com.sun.jdori.fostore.FOStorePMF
javax.jdo.option.ConnectionURL:fostore:/shared/databases/jdo/dbdir
javax.jdo.option.ConnectionUserName:craig
javax.jdo.option.ConnectionPassword:faster
javax.jdo.option.Optimistic:true
javax.jdo.option.NontransactionalRead:true

16.1.2 Servicing Requests

After your servlet has been initialized, the web container sends incoming requests to it. The web container dispatches each incoming HTTP request to service( ), which is implemented by the HttpServlet base class to call one of the HTTP service methods (doGet( ) or doPost( )) implemented by your servlet class. The following is a typical implementation of doGet( ) and doPost( ), which both delegate to processRequest( ). This implementation is not standard, but it is a common pattern used by tools that create servlets; it is part of the MovieInfo class.

    protected void doGet(HttpServletRequest request,
        HttpServletResponse response)
            throws ServletException, java.io.IOException {
        processRequest(request, response);
    }
    protected void doPost(HttpServletRequest request, 
        HttpServletResponse response)
            throws ServletException, java.io.IOException {
        processRequest(request, response);
    }
    protected void processRequest(HttpServletRequest request, 
        HttpServletResponse response)
            throws ServletException, java.io.IOException {
        pm = persistenceManagerFactory.getPersistenceManager(  );
        response.setContentType("text/html");
        java.io.PrintWriter out = response.getWriter(  );
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Servlet</title>");
        out.println("</head>");
        out.println("<body>");
        out.print(formatMovieInfo(  ));
        out.println("</body>");
        out.println("</html>");
        out.close(  );
        pm.close(  );
    }

16.1.3 PersistenceManager per Request

The following method actually performs the application-specific processing that requires the PersistenceManager. Implementing JDO datastore access as a method in the servlet is not recommended; it is presented only as an example. Note that the PersistenceManager is obtained from the PersistenceManagerFactory at the beginning of the processRequest( ) method and is closed at the end of the method. This pattern, known as PersistenceManager per Request, is a typical use of PersistenceManager in managed environments. If the request contained multiple methods, they would all use the same PersistenceManager.

    protected String formatMovieInfo(  ) {
        StringBuffer result = new StringBuffer(  );
        Extent movies = pm.getExtent(Movie.class, true);
        Iterator it = movies.iterator(  );
        while (it.hasNext(  )) {
            result.append("<P>");
            Movie movie = (Movie)it.next(  );
            result.append(movie.getDescription(  ));
        }
        return result.toString(  );
    }

16.1.4 PersistenceManager per Application

The PersistenceManager per Request pattern is the most common and arguably the most scalable approach to managing PersistenceManager instances. Another approach, PersistenceManager per Application, may offer better performance in certain situations.

With this pattern, there is a single PersistenceManager for all servlets and all requests in the application. This approach might be good for read-only applications that use a relatively small number of persistent instances and don't need transactions. Since multiple threads can execute request methods simultaneously, access to the PersistenceManager must be carefully controlled. Either the application needs to serialize access, or the PersistenceManager needs to have the Multithreaded property set to true.

You should keep the number of instances small to avoid growing the cache. With the PersistenceManager per Request pattern, most objects can be garbage-collected as soon as the request is done. But with a single PersistenceManager, newly instantiated instances in the cache tend to stay around for a long time. While the JDO implementation holds only a weak reference to persistent instances in the cache, managing the weak references might be a challenge for the garbage collector.

You should avoid transactions, because while one thread is committing a transaction, no other thread can access the cache. Even with the Multithreaded property set to true, only one thread can access the PersistenceManager during commit. The benefits of having cached instances can be overshadowed by poor concurrency during commit.

16.1.5 PersistenceManager per Transactional Request

If most requests are nontransactional, with a small number of transactional requests (insert, delete, or update), you can consider combining the common PersistenceManager approach with PersistenceManager per Transactional Request. This allows you to navigate the graph of persistent instances in the common cache to find the instance that needs to be updated, and then use a new PersistenceManager obtained from the same PersistenceManagerFactory to perform the transaction.

16.1.6 PersistenceManager per Session

Another approach for managing the PersistenceManager is to create a PersistenceManager and store it in a session attribute. While this makes some of the programming easier, it has significant disadvantages.

Implementations of the PersistenceManager generally do not support serialization, which is the specified implementation of a persistent session state. Therefore, the application cannot be distributable; all of the requests that are part of a session must be handled by the same server. Further, migration of sessions in case of system failure is not possible.

These aspects of the runtime environment reduce the scalability and robustness of your application, and we recommend that you carefully evaluate your reasons to use this pattern. As an alternative, you can store the identity instances of persistent instances in session attributes and obtain the persistent instances by using getObjectById( ) from the PersistenceManager obtained for the request. This is a scalable technique that avoids the problems associated with storing the PersistenceManager itself in the session state.

16.1.7 Transactions

For many requests, transactions are not required. Looking up information, browsing a datastore, or even displaying certain types of data for particular users does not necessarily require transactional guarantees. Thus, many requests can simply use the PersistenceManager to perform a query, navigate to some instances of interest to satisfy the request, retrieve some persistent fields, and close the PersistenceManager. But to add new instances, update instances in the datastore, or delete instances, you must begin and commit a transaction.

If you are deploying your servlet outside a J2EE server and don't have access to a UserTransaction, then you use the JDO Transaction to delimit transactions, using the begin( ), commit( ), and rollback( ) methods discussed in earlier chapters. In this environment, you cannot combine operations from multiple data sources into a single global transaction.

If you are deploying your servlet in a J2EE server, there are two mechanisms that you can use for managing transactions. The first is to use the JDO Transaction discussed previously. Using the JDO Transaction, your application is responsible for performing all the operations that are part of the same transaction using the same PersistenceManager. With this approach, you cannot coordinate transactions that involve multiple resources.

The second mechanism is to use a UserTransaction, available from the server via the JNDI lookup method. The instance that implements a UserTransaction is created and managed by the server. With a UserTransaction, you can demarcate J2EE transactions that span multiple data sources, and any operations done between the beginning and completion of the J2EE transaction will be coordinated with other operations. This allows you to use multiple resources (more than one JDO PersistenceManager, JDBC DataSource, EJB bean method, etc.) and combine all of their operations into one global transaction.

In order for the PersistenceManagerFactory to give you the PersistenceManager associated with the proper J2EE transaction, you call begin( ) on the UserTransaction prior to getting the PersistenceManager from the PersistenceManagerFactory. During the execution of getPersistenceManager( ), the PersistenceManagerFactory discovers that the UserTransaction is active and automatically begins the JDO transaction for you. The JDO Transaction is marked so that calling any of the JDO transaction completion methods is a user error. Instead, you must complete the J2EE transaction via UserTransaction commit( ) or rollback( ). The PersistenceManager is also marked so that, when it is closed by your application, it waits for the UserTransaction completion before being reused or discarded.

16.1.8 JavaServer Pages

JavaServer Pages technology provides an easy way to generate dynamic web content by embedding actions into HTML pages. The actions are either callouts to the Java language or references to library routines that encapsulate commonly needed functions, such as datastore access.

JSP pages allow construction of dynamic web content by using HTML editors to create prototype web pages. The dynamic content is interpreted by the HTML editor as just another tag that can be edited without further interpretation. With this approach, web content designers can use WYSIWYG (what you see is what you get) web-page editors, in which the dynamic content is displayed as text.

Using JSP pages effectively requires libraries of functions, called tag libraries. There are standard tag libraries, which include functions to access request parameters, access cookies, create and access scoped variables, query a JDBC database, iterate collections of transient or persistent instances, parse and transform XML documents, and display information from beans used in the JSP page.

At the time of this writing, there is no standard tag library to define access to JDO. The effort is underway, however.

The shape of a standard tag library for JDO can be seen by examining the JDBC tag library. There are tag elements to establish the factory, query the datastore, and demarcate transactions.

Until a standard tag library is available for JDO, code JSP pages using JDO with native Java code callouts from the page.

    [ Team LiB ] Previous Section Next Section