Chapter 10. JAX-RPC Service Endpoints

A JAX-RPC service endpoint (JSE) is the simplest way to create a Web service in J2EE. In fact, it's so simple to create basic endpoints with JSEs that you will probably be reluctant to try anything else. Of course, there is a lot more to JAX-RPC than JSEs, but this chapter will give you enough information to get up and running with your first Web service.

A Simple JSE Example

Writing the code for a JSE is extremely simple. To create a JSE all you do is define an endpoint interface and an implementation class. An endpoint interface defines each of the Web service operations that your JSE will support in the form of a Java method. The implementation class implements these endpoint methods. For example, Listing 10-1 shows an endpoint interface for the BookQuote Web service. It defines a single method, getBookPrice(), and thus a single Web service operation.

Example 10-1. An Endpoint Interface

package com.jwsbook.jaxrpc;

public interface BookQuote extends java.rmi.Remote {
  public float getBookPrice(String isbn) throws java.rmi.RemoteException;
}

The endpoint interface defines the Web service operations that will be publicly accessible—in other words, the operations that Web service clients can access using SOAP. The endpoint interface is required to extend (directly or indirectly) the java.rmi.Remote interface and to throw the java.rmi.RemoteException type from all of its methods.

In addition to the endpoint interface you must define the class that implements the endpoint methods. The implementation class will be instantiated and run inside the J2EE server. For example, the BookQuote_Impl_1 class in Listing 10-2 implements the BookQuote endpoint interface, and will serve as the Web service at runtime.

Example 10-2. An Implementation Class

package com.jwsbook.jaxrpc;

public class BookQuote_Impl_1 implements BookQuote {
  // Given the ISBN of a book, get its wholesale price.
  public float getBookPrice(String isbn){
      return 24.99f;
  }
}

In this example, BookQuote_Impl_1 is a very simple class, which always returns the same value. Later in this chapter we will add JDBC logic that will dynamically access a relational database to fetch the correct wholesale price for a specific ISBN number.

Once you have defined the endpoint interface and its implementation class, you are ready to deploy the JSE into your J2EE server. Most J2EE servers will provide you with GUIs or command-line utilities for doing this work; some will require you to create your own deployment descriptor files.

To run the BookQuote example and the rest of the examples in this book, you'll need to have a J2EE platform installed, the proper classpaths set up, and supporting services. Creating WAR files and deployment descriptors for JSEs is a lot more involved than defining the endpoint interface and the implementation class. Although general packaging and deployment descriptors are covered in Part VII, you will need to consult your vendor's documentation on setup, configuration, and administration.

The JSE Runtime Environment

JSEs are deployed into a J2EE servlet container, and have access to the same resources and context information a servlet has. When a JSE is deployed, it is embedded in a special JAX-RPC servlet provided by the vendor, which is responsible for responding to HTTP-based SOAP requests, parsing the SOAP messages, and invoking the corresponding methods of the JSE implementation object. When the JSE returns a value from the method invocation, the JAX-RPC servlet creates a SOAP message to hold the return value (or a SOAP fault if an exception is thrown) and sends that SOAP message back to the requesting client via an HTTP reply message. Figure 10-1 illustrates how a JAX-RPC servlet receives and delegates SOAP calls to a JSE.

A JAX-RPC Servlet Delegates to a JSE

Figure 10-1. A JAX-RPC Servlet Delegates to a JSE

Invoking a JSE Web service as illustrated in Figure 10-1 proceeds as follows:

  1. A Web services client sends a SOAP 1.1 request message over HTTP 1.1 to an endpoint URL that is hosted by the J2EE application server.

  2. A JAX-RPC servlet receives the SOAP message and marshals it into a method call on the JSE object.

  3. The JSE processes the request and returns the result to the JAX-RPC servlet.

  4. The JAX-RPC servlet converts the return value (or exception) into a SOAP message and sends it back to the client in the HTTP reply.

Vendors may generate special JAX-RPC servlets for each JSE, or multiplex all requests over a few general-purpose JAX-RPC servlets. The exact mechanics of the JAX-RPC servlet are not specified; vendors provide their own proprietary JAX-RPC servlets. That's OK, because you'll never deal directly with the JAX-RPC servlet anyway.

Because the JSE is embedded in a servlet, it can access the same resources the servlet can, via the JNDI Environment Naming Context (ENC). JSEs can access JDBC drivers, JMS providers, EJBs, J2EE connectors, other Web services, environment variables, the ServletContext, the HttpSession, and anything else a servlet can access. In addition, the JSE is allowed access to the actual SOAP message to which it's responding—which can be useful for examining header blocks and other information that is not explicitly passed as method parameters.

Servlets: The Foundation of JSE

The JAX-RPC service endpoint is built on the servlet programming model. Servlets have been evolving and improving since 1997. They have proven to be robust and efficient server-side components for processing HTTP requests for Web pages and, more recently, for Web service endpoints. One of the reasons servlets have been so successful is that, on the surface, they are extremely simple. When a Web browser makes a request for a Web page, it sends an HTTP GET (in most cases) to the servlet container system. The servlet container system picks an instance of the appropriate servlet to handle the HTTP request. The servlet instance is given an input stream representing the request and an output stream to send back a response to the browser. For example, the HelloWorldServlet (Listing 10-3) is designed to return a Web page that says, “Hello World!” At deployment time, it's assigned to handle all requests to the Web page address http://www.monson-haefel.com/jwsbook/helloworld.html.

Example 10-3. A Simple Servlet

package com.jwsbook.jaxrpc;

import javax.servlet.http.*;
import javax.servlet.*;

public class HelloWorldServlet extends javax.servlet.http.HttpServlet {
  protected void doGet(HttpServletRequest req,
                       HttpServletResponse resp)
  throws ServletException,java.io.IOException {

    java.io.Writer outStream = resp.getWriter();
    outStream.write("<html><body>Hello World!</body></html>");
  }
}

This is a really basic servlet that illustrates the simplicity and elegance of the servlet programming model. It's possible to create far more complex servlets that can obtain more information from the calling browser and send far more sophisticated content back, but even those advanced capabilities build on the simple programming model illustrated in Listing 10-3. In a nutshell, servlets focus on processing HTTP requests as input and output streams.

When servlets were first introduced back in 1997, it was a fairly radical approach to a problem that had traditionally been solved using CGI (Common Gateway Interface). As servlets increased in popularity and attracted a broader audience, Sun decided to make things a little easier for less sophisticated developers by introducing JavaServer Pages (JSPs), which combine HTML with specialized tags and Java code. While JSPs today are perhaps more popular than servlets, it's important to understand that JSPs are in fact servlets. The only difference is the source code. To develop servlets, you create Java source files. To develop JSPs, you write everything in HTML and JSP tags—which is then compiled into a servlet. When you're talking about servlets and JSP, you are really talking about the same thing.

This is also true of JAX-RPC service endpoints. On the surface they look like a whole new type of component, and officially they are—but under the covers, JSEs are built on the servlet programming model.

JNDI Environment Naming Context

All J2EE components, including servlets, and therefore JSEs, have access to a private set of environment variables and resources using JNDI (Java Naming and Directory Interface). JNDI is usually used for access to a naming or directory service. A naming service binds objects or resources to names. A typical example of a naming service is DNS (Domain Name System), which maintains a distributed database that maps Internet domain names to IP addresses—you use DNS every time you look up a Web page with your browser. A directory service also binds names to objects or resources and can organize bindings into a directory structure analogous to the directory structure used by your file system. Examples of directory systems include LDAP (Lightweight Directory Access Protocol) and its more complex ancestor X.500.

J2EE defines a very simple directory service, called the JNDI Environment Naming Context (JNDI ENC), which allows developers to bind resources (JDBC and JMS connection factories, for example), enterprise beans, Web service endpoints, and environment variables into a directory structure. The JNDI ENC is frequently used to gain access to a JDBC connection, so data can be read from and and written to a relational database. For example, in Listing 10-4 we beef up the BookQuote JSE implementation class by adding code that uses the JNDI ENC to obtain a JDBC connection, then using that connection to fetch the wholesale price of a book from a database.

Example 10-4. A JSE Using the JNDI ENC to Get a JDBC Connection

package com.jwsbook.jaxrpc;

public class BookQuote_Impl_2 implements BookQuote {
  public float getBookPrice(String isbn){
    java.sql.Connection jdbcConnection = null;
    java.sql.Statement sqlStatement = null;
    java.sql.ResultSet resultSet;
    try {
      javax.naming.InitialContext jndiEnc =
                               new javax.naming.InitialContext();
      javax.sql.DataSource dataSource = (javax.sql.DataSource)
                  jndiEnc.lookup("java:comp/env/jdbc/DataSource");
      jdbcConnection = dataSource.getConnection();
      sqlStatement = jdbcConnection.createStatement();
      resultSet = sqlStatement.executeQuery(
        "SELECT wholesale FROM CATALOG WHERE isbn = '"+isbn+"'");

      if(resultSet.next()){
        float price = resultSet.getFloat("wholesale");
        return price;
      }
      return 0;// zero means it's not stocked.
    }catch (java.sql.SQLException se) {
      throw new RuntimeException("JDBC access failed");
    }catch (javax.naming.NamingException ne){
      throw new RuntimeException("JNDI ENC access failed");
    }
  }
}

The JNDI ENC is a standard service available to all J2EE components, but each component has a unique view of the service. A component may access its own view of the JNDI ENC, but not the view of any other component. Every component's view of the JNDI ENC is fixed at deployment time and is immutable, which means that it's read-only. You cannot insert, remove, or modifiy values in the JNDI ENC at runtime.

The InitialContext object represents the root of the JNDI ENC hierarchy. When a JSE creates a new InitialContext, the servlet container automatically intervenes—under the covers—to return the root of the JNDI ENC for that JSE component. Every JSE, just like every servlet, has its own view of the JNDI ENC, which you configure before deploying the JSE. The following snippet from Listing 10-4 shows how the JNDI InitialContext object is created.

javax.naming.InitialContext jndiEnc =
                         new javax.naming.InitialContext();

The InitialContext defines an abundance of methods for perusing subcontexts (subdirectories), binding objects to subcontexts, and creating and removing subcontexts. Most of these methods will not work on the JNDI ENC itself, however, because the JSE's view of the JNDI ENC is immutable. The JSE cannot modify it in any way. In most cases the only method of InitialContext you are likely to use is the lookup() method, which takes a directory path as its only argument and returns whatever object is bound to that path. The following snippet from Listing 10-4 shows how the JSE invokes lookup() to obtain a reference to a JDBC DataSource.

javax.sql.DataSource ds = (javax.sql.DataSource)
                        jndiEnc.lookup("java:comp/env/jdbc/DataSource");

Resources like the JDBC DataSource in the previous example are bound to names that you declare in the deployment descriptor of the JSE. The root context of the JNDI ENC is always "java:comp/env", but you can define any path relative to the root context you want. For example, you could have bound the JDBC DataSource to "java:comp/env/mydirectory/database/BookDatabase". There are, however, naming conventions that most J2EE projects follow: JDBC DataSource objects are placed under the "/jdbc" subcontext; references to EJB objects are placed under the "/ejb" subcontext; JavaMail uses the "/mail" subcontext; JMS uses the "/jms" subcontext; and Web services use the "/services" subcontext. At the end of a directory path is the name that you assign to the object being referred to. There are no conventions for that name, except that it should be descriptive. This book tends to use names that describe the type of object being returned or the identity of the resource—for example, DataSource for a JDBC DataSource.

In the preceding example, the JSE uses the JNDI ENC to gain access to a JDBC DataSource object, which is a factory for creating JDBC connections, as shown in this snippet from Listing 10-4:

javax.sql.DataSource dataSource = (javax.sql.DataSource)
                        jndiEnc.lookup("java:comp/env/jdbc/DataSource");
jdbcConnection = dataSource.getConnection();

Looking up a resource factory and then using it to obtain resource connections is the normal pattern when using the JNDI ENC. For example, you can use the JNDI ENC to obtain a JMS ConnectionFactory, a JavaMail Session, or a J2EE ConnectionFactory. See Listings 10-5 through 10-7.

Example 10-5. Using the JNDI ENC to Access a JMS ConnectionFactory

javax.naming.InitialContext jndiEnc = new javax.naming.InitialContext();
javax.jms.ConnectionFactory conFactory =(javax.jms.ConnectionFactory)
  jndiEnc.lookup("java:comp/env/jms/ConnectionFactory");
javax.jms.Connection connection =
  conFactory.createConnection(username,password);

Example 10-6. Using the JNDI ENC for Access to a JavaMail Session Object

javax.naming.InitialContext jndiEnc = new javax.naming.InitialContext();
javax.mail.Session session = (javax.mail.Session)
  jndiEnc.lookup("java:comp/env/mail/Session");
javax.mail.internet.MimeMessage email = new
  javax.mail.internet.MimeMessage(session);

Example 10-7. Using the JNDI ENC for Access to an Arbitrary J2EE Connector

javax.naming.InitialContext jndiEnc = new javax.naming.InitialContext();
javax.resource.cci.ConnectionFactory factory =
 (javax.resource.cci.ConnectionFactory)
   jndiEnc.lookup("java:comp/env/connector/VendorX");
javax.resource.cci.Connection con = factory.getConnection();

You can also use the JNDI ENC to access EJBs. For example, instead of using JDBC to access the database directly, you might use a Book EJB, an entity bean that represents an individual book in the database. Listing 10-8 shows how the BookQuote JSE would access the Book EJB to get the wholesale price and return it to the requester.

Example 10-8. A JSE Implementation Object Using EJB

package com.jwsbook.jaxrpc;
import  jwsed1.support.BookLocal;
import  jwsed1.support.BookHomeLocal;

public class BookQuote_Impl_3 implements BookQuote {
  public float getBookPrice(String isbn){
    try {
      javax.naming.InitialContext jndiEnc =
                    new javax.naming.InitialContext();
      BookHomeLocal bookHome =(BookHomeLocal)
                    jndiEnc.lookup("java:comp/env/ejb/BookHomeLocal");
      BookLocal book = bookHome.findByPrimaryKey(isbn);
      return book.getWholesalePrice();
    }catch(javax.naming.NamingException ne){
      throw new RuntimeException("JNDI ENC access failed");
    }catch(java.rmi.RemoteException re){
      throw new RuntimeException("Problem invoking operation on Book EJB");
    }catch(javax.ejb.FinderException fe){
      throw new RuntimeException("Cannot find Book EJB");
    }
  }
}

You can also use the JNDI ENC to access other Web services via the JAX-RPC programming model. For example, the BookQuote JSE might obtain the pricing information from a legacy system such as CICS or IMS by way of some other Web service written in some other programming language. Listing 10-9 illustrates.

Example 10-9. A JSE Implementation Object Using JAX-RPC Generated Stubs

package com.jwsbook.jaxrpc;
import jwsed1.support.CatalogPort;
import jwsed1.support.ImsCatalogService;

public class BookQuote_Impl_4 implements BookQuote {
  public float getBookPrice(String isbn){
    try {
      javax.naming.InitialContext jndiEnc = new javax.naming.InitialContext();
      ImsCatalogService webService =(ImsCatalogService)
               jndiEnc.lookup("java:comp/env/service/ImsCatalogService");
      CatalogPort catalog = webService.getCatalogPort();
      return catalog.getWholesalePrice(isbn);
    }catch (javax.xml.rpc.ServiceException se) {
            throw new RuntimeException("JAX-RPC ServiceException thrown");
    }catch (javax.xml.rpc.JAXRPCException je) {
            throw new RuntimeException("JAXRPCException thrown");
    }catch (java.rmi.RemoteException re){
            throw new RuntimeException("RemoteException thrown");
    }catch (javax.naming.NamingException ne){
            throw new RuntimeException("NamingException thrown");
    }
  }
}

The ImsCatalogService and CatalogPort interfaces and the code that implements them are generated at deployment time by the J2EE vendor's JAX-RPC toolkit. This is a hypothetical example that I won't elaborate on. The steps for creating JAX-RPC generated stubs like this one are discussed in detail in Section 12.1: Generated Stubs.

A JSE can also access environment variables from the JNDI ENC. Environment variables are static; they cannot be changed. They can be of any Java primitive wrapper type (Byte, Character, Short, Integer, Long, Float, or Double) or a String value. Environment variables are often used for configuring variables used by a JSE. Listing 10-10 illustrates how the JNDI ENC can be used to obtain both numerical and String values.

Example 10-10. Accessing Environment Variables from a JSE

javax.naming.InitialContext jndiEnc = new javax.naming.InitialContext();
Double maxValue = (Double)jndiEnc.lookup("java:comp/env/max_value");
Boolean flag = (Boolean)jndiEnc.lookup("java:comp/env/the_flag");
String name = (String)jndiEnc.lookup("java:comp/env/some_name");

You configure the JNDI ENC at deployment time, coding XML elements of the deployment descriptor to identify the resources, EJBs, Web services, and environment variables, and bind them to specific path names for a specific JSE. Part VII: Deployment describes in detail the deployment descriptors and the JNDI ENC, but Listing 10-11 illustrates how the declarations are defined in the deployment descriptor for a JSE.

Example 10-11. A Deployment Descriptor for a JSE (Abbreviated)

<web-app>
  <display-name>BookQuoteJSE</display-name>
  ...
  <resource-ref>
    <res-ref-name>jdbc/BookDatabase</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
  </resource-ref>
  <env-entry>
    <env-entry-name>max_value</env-entry-name>
    <env-entry-type>java.lang.Double</env-entry-type>
    <env-entry-value>3929.22</env-entry-value>
  </env-entry>
  <ejb-local-ref>
    <ejb-ref-name>/ejb/BookHomeLocal</ejb-ref-name>
    <ejb-ref-type>Entity</ejb-ref-type>
    <local-home>jwsed1.support.BookHomeLocal</local-home>
    <local>jwsed1.support.BookLocal</local>
    <ejb-link>BookEJB</ejb-link>
  </ejb-local-ref>
  ...
</web-app>

When the JSE is deployed, the container will ensure that the resources, EJBs, and Web services declared in the deployment descriptor are live, working references. When you look up a JDBC DataSource, you will receive a live connection to a database. Similarly, when you look up an EJB or Web service, you will receive a working reference to an EJB or a Web service endpoint. The person who deploys the JSE into the J2EE application server is responsible for mapping the resource, EJB, and Web service references to actual resources, using tools provided by the J2EE vendor. In the case of a JDBC DataSource, for example, the deployer must map the DataSource object to an actual database using a specific JDBC driver.

Every J2EE vendor differs in the exact mechanics of how these mappings are achieved. In most cases, however, the vendor will provide ancillary configuration files (as BEA WebLogic does) or a graphical user interface (GUI) tool (as IBM WebSphere does).

The ServletEndpointContext and ServiceLifecycle Interfaces

In addition to the JNDI ENC there is another API that a JSE can use to interact with its environment, the ServletEndpointContext. This is an interface to the servlet container system itself. It provides the JSE with access to the underlying ServletContext, SOAP messages, and other information.

Using the Life-Cycle Methods init() and destroy()

To take advantage of the ServletEndpointContext, the JSE must implement the optional javax.xml.rpc.server.ServiceLifecyle interface, which defines two methods, as shown in Listing 10-12.

Example 10-12. The javax.xml.rpc.server.ServiceLifecycle Interface

package javax.xml.rpc.server;
import javax.xml.rpc.ServiceException;

public interface ServiceLifecycle {
    public void init(Object context) throws ServiceException;
    public void destroy();
}

The init() method is called at the beginning of a JSE's life, just after it's instantiated by the container system, before it begins handling incoming SOAP requests. When the servlet container calls the init() method, the JSE is given a reference to its ServletEndpointContext. The servlet container calls the destroy() method at the end of the JSE's life, just before it is removed from service.

Depending on the J2EE vendor and the threading model used, a servlet container may use a single JSE instance to handle all incoming requests, or multiple instances—this subject is discussed in more detail in Section 10.3. When a new instance of a JSE is needed, one is instantiated and its init() method is called. The init() method is called only once in the life of a JSE instance. If the servlet container has more JSE instances than it needs for a particular Web service endpoint, it may choose to evict some of the instances from memory to conserve resources. Each JSE instance's destroy() method is called when the servlet container evicts it, when the JSE is removed from service, or when the J2EE server is shut down.

Because init() is called at the beginning of the JSE instance's life, and destroy() is called at the end, it makes sense to use these methods to obtain and release resources that will be used for the entire life of the instance. For example, instead of the JSE creating a new InitialContext and looking up the JDBC DataSource every time getBookPrice() is called, as in Listing 10-4, its init() method can create the InitialContext just once, and use it immediately to obtain a reference to the JDBC DataSource. The DataSource reference can then be held in an instance variable for the life of the JSE and reused as needed. Later, when the JSE instance is about to be removed from service, its destroy() method can release the DataSource by setting the reference to it to null. Listing 10-13 shows the BookQuote JSE implementing the ServiceLifecycle interface. It obtains its JDBC DataSource in init() and releases it in destroy(). In init() it also obtains a reference to its ServletEndpointContext.

Example 10-13. A JSE Implementing the ServiceLifecycle Interface

package com.jwsbook.jaxrpc;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.xml.rpc.server.ServletEndpointContext;
import javax.xml.rpc.server.ServiceLifecycle;
import javax.xml.rpc.ServiceException;

public class BookQuote_Impl_5 implements BookQuote, ServiceLifecycle {
  javax.sql.DataSource dataSource;
  ServletEndpointContext endPtCntxt;

  public void init(Object context) throws ServiceException{
    try{
      endPtCntxt = (ServletEndpointContext)context;
      InitialContext jndiEnc = new InitialContext();
      javax.sql.DataSource dataSource = (javax.sql.DataSource)
                           jndiEnc.lookup("java:comp/env/jdbc/BookDatabase");
    }catch(NamingException ne){
      throw new ServiceException("Cannot initialize JNDI ENC", ne);
    }
  }
  public float getBookPrice(String isbn){
    java.sql.Connection jdbcConnection = null;
    java.sql.Statement sqlStatement = null;
    java.sql.ResultSet resultSet;
    try {
      jdbcConnection = dataSource.getConnection();
      sqlStatement = jdbcConnection.createStatement();
      resultSet = sqlStatement.executeQuery(
        "SELECT wholesale FROM CATALOG WHERE isbn = '"+isbn+"'");

      if(resultSet.next()){
        float price = resultSet.getFloat("wholesale");
        return price;
      }
      return 0;// zero means it's not stocked.
    }catch (java.sql.SQLException se) {
      throw new RuntimeException("JDBC access failed");
    }
  }
  public void destroy( ){
    dataSource = null;
  }
}

In actual practice, you probably don't need to set the DataSource reference to null when you are done using it. The container should clean it up for you automatically. Setting the DataSource reference to null in this example demonstrates the basic purpose of the destroy() method: to release resources before the JSE instance is destroyed.

You may be asking yourself, “Why does the init() method declare the context parameter as an Object type and not a ServletEndpointContext?” Good question! The ServiceLifecycle interface is intended to be a general-purpose interface that can be used outside of J2EE and a servlet container system—it might be running in some other type of container. JAX-RPC defines the context parameter as an Object type, so the interface is compatible with other, unanticipated or proprietary, container systems. In J2EE Web services, however, the JAX-RPC service endpoint is the only kind of component that implements the ServiceLifecycle interface, and because it runs in a servlet container, it naturally makes use of the ServletEndpointContext.

Using the ServletEndpointContext Interface

A JSE instance obtains a reference to its ServletEndpointContext when its init() method is invoked—assuming it implements the ServiceLifecyle interface. The servlet container calls init() only once on each instance, so the JSE instance must preserve the reference in an instance variable if it wants to have access to it later.

The ServletEndpointContext is the JSE's interface to its servlet container. It provides access to the identity of the client making a request, session data, the servlet context, and the message context used by JAX-RPC handlers. To make these services available, the servlet container must provide an implementation of the ServletEndpointContext interface at runtime. The interface is shown in Listing 10-14.

Example 10-14. The javax.xml.rpc.server.ServletEndpointContext Interface

package javax.xml.rpc.server;
public interface ServletEndpointContext {
    public java.security.Principal getUserPrincipal();
    public boolean isUserInRole(String role);
    public javax.xml.rpc.handler.MessageContext getMessageContext();
    public javax.Servlet.http.HttpSession getHttpSession()
      throws javax.xml.rpc.JAXRPCException;
    public javax.Servlet.ServletContext getServletContext();
}

Although a JSE instance maintains a reference to the same ServletEndpointContext object throughout its life, the values returned by its methods will change with every new SOAP request. The information obtained by the ServletEndpointContext is actually obtained via the JAX-RPC servlet that wraps the JSE instance. The Principal and HttpSession objects, returned by the getUser Principal() and getHttpSession() methods, are obtained from the servlet's doPost() method's HttpServletRequest parameter. Similarly, the ServletContext, returned by the getServletContext() method, is a reference to the JAX-RPC servlet's ServletContext. The JAX-RPC servlet also instantiates and manages the MessageContext, returned by getMessageContext(), which is used by the JAX-RPC handler chain.

The getUserPrincipal() and isUserInRole() Methods

The ServiceEndpointContext.getUserPrincipal() method returns the identity of the sender, the SOAP client that is making the request. The java.security .Principal object represents the identity of the application that is sending the SOAP message and is available only if the JSE was configured to use either HTTP Basic Authentication (BASIC-AUTH) or Symmetric HTTP. If no authentication is used, the getUserPrincipal() method returns null.

BASIC-AUTH requires that the SOAP sender provide a user-id and password, which are passed to the servlet container in a special HTTP request message. The servlet container will use the user-id and password to authenticate the SOAP sender against some kind of user database. The Principal object is derived from the identity obtained from the database. BASIC-AUTH can be set up during deployment and is usually used in combination with SSL (using the HTTPS protocol) for confidentiality (so the password is concealed when it's passed to the server). BASIC-AUTH is the most commonly used type of authentication.

Symmetric HTTP is a more sophisticated authentication mechanism, and more reliable because it's more difficult for hackers to fake. Symmetric HTTP requires both the servlet container and the SOAP sender to authenticate each other using X.509 digital certificates.[1] While Symmetric HTTP is stronger than BASIC-AUTH, it also requires more sophisticated SOAP clients, that can maintain and utilize X.509 digital certificates—a feature that most SOAP toolkits today do not support. This type of authentication is not very common.

Once a SOAP sender has been authenticated, using BASIC-AUTH or Symmetric HTTP, the same Principal object can be associated with the SOAP sender's subsequent messages by using session tracking, via SSL, cookies, or some other session-tracking method. Maintaining this association precludes having to reauthenticate every time the SOAP sender makes a request.

Regardless of the authentication mechanism used, the Principal represents the identity of the SOAP sender and the JSE can use it for logging. The Principal object may be propagated (for authorization) to any resources or EJBs accessible by the JSE, depending on the run-as and res-auth deployment settings in the deployment descriptor. See Part VII: Deployment for details.

A Principal may represent a specific person or system that assumes a variety of roles. For example, a bank employee might assume roles such as Teller, Bank Manager, and Employee. Roles are assigned to Principals using vendor-specific security tools. You can test callers to see if they belong to a specific role—assuming they have been authenticated—by calling the isUserInRole() method.

The getHttpSession() Method

The ServletEndpointSession.getHttpSession() method returns a javax .Servlet.http.HttpSession object, which represents an HTTP session that is associated with the SOAP sender. An HTTP session is a continuing conversation between the SOAP sender and the servlet container. It's an identifier that allows the servlet container to associate multiple HTTP requests with a specific SOAP sender. Session tracking is not used with Web services very much today, but customary practice will probably change as implementations become more complex and the technology matures. When HTTP session tracking is used, it's usually done with either cookies or SSL.[2]

Cookies are identifiers that are sent from the servlet container to the SOAP sender and are associated with a specific host, such as monson-haefel.com. Every time the SOAP sender makes an HTTP request, it includes the cookie, which allows the servlet container to match the request to a specific session. If HTTP Basic Authentication is used, the cookie will be established at that time. Cookies do not require authentication, however. Anonymous SOAP senders can also be assigned cookies so that the servlet container can track requests within a single session. The servlet container doesn't know who all the SOAP clients are, but does know which requests come from which client.

SSL (Secure Sockets Layer) uses encryption keys to establish a secure communication channel between an HTTP client and an HTTP server. Basically, the client and server agree to use a private encryption key for the duration of the session, so that all messages are encrypted and decrypted using that key. All this is done behind the scenes and is fairly quick. You have probably used SSL while sending credit card or other personal information to a Web site like Amazon.com. When your Web browser shows a closed lock in a corner of the frame, it's telling you that you have a secure SSL connection with the Web site. As long as the lock symbol is closed, all the HTTP traffic between your browser and the HTTP Web server is encrypted and cannot be read by other parties.[3] A SOAP application can use the same mechanism for confidential communications with a JSE. SSL has session tracking built right into the protocol, so when SSL is used, you get session tracking as a bonus.

Regardless of the mechanism used to track a session, the HttpSession object is used to represent the session itself. The HttpSession object allows the JSE to view and change information about the session, such as the session identifier, creation time, last access time, and when the session will time out. In addition, the JSE can associate custom attributes with a session, so that it can maintain state across invocations by the same client. An attribute is a name-value pair, where the value is any serializable object. If HTTP session tracking is not used, ServletEndpoint Context.getHttpSession() will return null.

In theory, Web services are supposed to be completely stateless, so the use of sessions and the storage of data associated with sessions are somewhat controversial. That said, there are circumstances in which session tracking and session data are necessary to improve the usability or performance of a Web service. For example, a Web service might use session tracking to ensure that clients receive information in their native language or currency. Most clients in the United States use English and the dollar, while those in France use French and the euro. Initially, the Web service might fetch language and currency preferences from the database, but once these are obtained they can be cached in the HttpSession to avoid repeated access to the database for the same information. In Listing 10-15, the InternationalJSE_Impl type uses the HttpSession object to track language and currency preferences of a specific client session.

Example 10-15. Using an HttpSession to Cache Session-Specific Data

package com.jwsbook.jaxrpc;
...
public class InternationalJSE_Impl implements InternationalJSE, javax.xml.rpc.server
Using an HttpSession to Cache Session-Specific Data.ServiceLifecycle {
  javax.sql.DataSource dataSource;
  ServletEndpointContext servletEndpointContext;

  public void init(Object context) throws ServiceException{
    ...
  }
  public void destroy(){
    ...
  }
  public void someMethod( ) {
    HttpSession httpSession = servletEndpointContext.getHttpSession();
    Principal principal = servletEndpointContext.getUserPrincipal();
    if( httpSession != null){
      // Get preferences from HttpSession object
      String language_preference = (String)
                                httpSession.getAttribute("language");
      String currency_preference = (String)
                                httpSession.getAttribute("currency");
      if( language_preference == null || currency_preference == null) {
        // Get preferences from database and initialize session data
        java.sql.Connection jdbcConnection = null;
        java.sql.Statement sqlStatement = null;
        java.sql.ResultSet resultSet;
        try {
          jdbcConnection = dataSource.getConnection();
          sqlStatement = jdbcConnection.createStatement();
          resultSet = sqlStatement.executeQuery(
                     "SELECT language, currancy FROM PREFERENCES "+
                     "WHERE Principal = "+principal.getName());
          if(resultSet.next()){
            language_preference = resultSet.getString("language");
            currency_preference = resultSet.getString("currency");
            // set attributes on HttpSession to avoid DB access.
            httpSession.setAttribute("language", language_preference);
            httpSession.setAttribute("language", currency_preference);
          }
        }catch (java.sql.SQLException se) {
            // handle SQLException
        }
      }
    }
    // Use the language and currency preferences in further processing
  }
}

The session logic in Listing 10-15 could be used in any Web service to obtain and cache the language and currency preferences of each SOAP sender. If the HTTP session is being tracked, the JSE can obtain the preferences from the database on the first access, then store them in the HttpSession object, where it can get them for any subsequent requests by the same SOAP sender.

The getServletContext() Method

The ServletEndpointContext.getServletContext() method returns the ServletContext object owned by the JAX-RPC servlet that wraps the JSE instance. The ServletContext acts as an interface to the servlet container system and provides access to name-value attributes, initialization parameters, files, files' MIME types, path-URL conversions, container version and brand information, logging, and a RequestDispatcher (which allows one servlet to call another servlet, JSP, or HTML page). The usefulness of the ServletContext to a JSE depends in large part on the specific needs of your Web service. Most JSEs will not need to use the ServletContext except for accessing initialization parameters and perhaps logging. If you need the other methods, they're available to you.

Table 10-1 lists and describes some of the methods defined by the ServletContext interface.

Table 10-1. ServletContext Methods

javax.Servlet.ServletContext

Description

getInitParameter(String)

Returns the String value of an initialization parameter set in the deployment descriptor. Returns null if there is no matching value.

getInitParameterNames()

Returns an Enumeration of all the named initialization parameters set in the deployment descriptor.

getAttribute(String)

Returns the Object value of a name-value pair. The servlet container or an individual servlet may set attributes, which are available to any servlet, JSP, or JSE in the same context.

setAttribute(String, Object)

Sets an attribute for this context. The attribute value can be any type of Object and will be accessible to any servlet, JSP, or JSE in the same context.

removeAttribute(String)

Removes an attribute from this context.

log(String)

Writes a text message to the servlet log file.

log(String, Throwable)

Writes a text message and stack trace for the Throwable (Exception or Error) to the servlet log file.

getResource(String)

Returns a java.net.URL object for a specific resource located in the servlet's file system or a WAR file. The path must begin with a forward slash ('/').

getResourcePaths(String)

Returns a java.util.Set of the paths of all the resources available, relative to the path passed to the method. The path must begin with a forward slash ('/').

getResourceAsStream(String)

Returns the resource (GIF, JPEG, HTML, or XML file, for instance) located at the path passed into the method as a java.io.InputStream.

getMimeType(String)

Returns the MIME type of the specified file at a given path.

getNamedDispatcher(String)

Returns a javax.Servlet.RequestDispatcher, which can be used to forward requests to other servlets, JSPs, HTML files, or JSEs.

getContext(String)

Returns a javax.Servlet.ServletContext object for a Web application located at a specified URL.

getRealPath(String)

Returns the absolute path, as a String, for a given relative path.

getServletContextName()

Returns the display name, as specified in the web.xml deployment descriptor of the Web application to which this servlet belongs.

getServletInfo()

Returns the name and version number of the servlet container.

getMajorVersion()

Returns the major version of the Java Servlets API this container supports.

getMinorVersion()

Returns the minor version of the Java Servlets API this container supports.

The getMessageContext() Method

The ServletEndpointContext.getMessageContext() method returns a javax.xml.rpc.handler.MessageContext object that is used to share information among handler objects and the JSE implementation object. Message handlers are designed to pre- and post-process the SOAP messages exchanged between J2EE port components (JSEs and EJB endpoints) and SOAP clients. A port component can be configured to use a chain of handler objects, each of which does some pre- and/or post-processing on the SOAP message. Handler objects in a chain share information during the processing of a message using the MessageContext, which defines methods for setting and accessing properties in the form of name-value pairs. The MessageContext is covered in detail in Chapter 14.

Multi-threading and JSEs

Servlets, and therefore JSEs, support two basic threading models: multi-threaded and single-threaded. To understand these two programming models you have to understand Java threading—if you don't, I recommend you read up on the subject before tackling this section. A good book on threading is the second edition of Concurrent Programming in Java: Design Principles and Patterns, by Doug Lea.

When a JSE is multi-threaded, all client requests access exactly the same JAX-RPC servlet and JSE instance. Each client request is associated with its own thread of execution, and many threads will access the same JSE instance simultaneously. This model allows a single instance to support hundreds of clients, but it also requires care when accessing instance or class-level variables. Because all threads see the same instance and class variables, access to these variables should be avoided or synchronized using synchronized methods or blocks.

When a JSE is single-threaded, the servlet container normally maintains a pool of JAX-RPC servlets, and therefore JSE instances, then plucks them from the pool to handle client requests. Each simultaneous client request accesses a different JSE instance, so you never have multiple threads accessing the same JSE instance at the same time. Once a client request ends, the JSE instance is returned to the pool so that it can be used to handle another request. You don't have to worry about instance variables; they can be accessed freely without using synchronization. You should avoid creating static variables, on the other hand, or synchronize access to them, just as in the multi-threaded model. The single-threaded programming model has turned out to work poorly in practice because it degrades performance and introduces resource-management problems the multi-threaded model doesn't suffer from. For the most part, servlet experts recommend that you do not use the single-threaded programming model.

The single-threaded model was deprecated as of Java Servlets 2.4, which is the specification supported by J2EE 1.4. In the future the single-threaded model will be phased out of the specification, and only the multi-threaded model will be supported. You can still use the single-threaded model—that's why it's covered in this book—but it's discouraged by the specification.

Which threading model a JAX-RPC servlet and its JSE use depends on whether the JAX-RPC servlet implements the javax.Servlet.SingleThreadModel interface. A JSE that implements the SingleThreadModel interface can be accessed by only one thread at a time—the assumption being that a pool of instances will be used to service requests.

Currently there is no vendor-agnostic way to specify whether a JSE should use a single- or multi-threaded programming model. Your vendor may provide some method for specifying the threading model, or it may support only one threading model for JSEs. Consult your vendor's documentation for details.

Wrapping Up

JSEs are fairly simple to implement. You only need to define a Java implementation class and an endpoint interface. This simplicity masks a fairly complex and powerful runtime environment available to JSEs, however. Because a JSE is actually embedded in a JAX-RPC servlet, it can exploit the powerful servlet container system that has proven so useful in Web application development since 1997. The JSE effectively has access to most, if not all, of the same resources and APIs a servlet has at runtime.

As you learned in this chapter, a JSE can use the JNDI ENC to access JDBC drivers, JMS, J2EE connectors, EJBs, environment entries, and other Web services. If the JSE implements the ServiceLifecycle interface, it will be given a reference to the ServletEndpointContext, which provides the JSE with an API for accessing the Principal, the HttpSession, and the ServletContext of the JAX-RPC servlet, as well as the MessageContext used by the JSE's handlers.

The JSE is one of two types of Web service that can be deployed in J2EE. The other is the EJB endpoint, which is a stateless session bean that can process SOAP messages just as the JSE can. The main difference is that the EJB endpoint provides more robust transaction-processing capabilities, which tend to be useful when accessing resources that require distributed transactions, or when managing the interaction of several resources and enterprise beans. EJB endpoints are discussed in more detail in Chapter 11. In most cases, though, you will not need the robust transaction-processing capabilities of an EJB endpoint. Because the EJB endpoint programming model is more complex than the JSE programming model, and because EJBs tend to introduce more overhead and less throughput, it's best to avoid them unless you really need them.

If you have read this entire chapter, then you know enough about JSEs to be truly dangerous, which means you have enough knowledge to implement a Web service but you should learn more before implementing a production system. You don't have to read every chapter in this part of the book now, but you should study the other chapters when you have time. As always, read what you need, and use the rest as reference during development.



[1] Internet Engineering Task Force, “RFC 2459: Internet X.509 Public Key Infrastructure Certificate and CRL Profile” (1999). Available at http://www.ietf.org/rfc/rfc2459.txt.

[2] URL rewriting and session tracking based on hidden forms are not used in SOAP-based Web services because they require the exchange of HTML, which isn't used in SOAP communications.

[3] When SSL is used with HTTP, it's called HTTPS.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.31.141