Creating Web Services with WebLogic Server


In this section, we show you how to build and deploy Web Services using WebLogic Server. Before we dive into the details, we briefly describe WebLogic Server s Web Services container architecture. Next , we discuss the general options and strategies for developing Web Services with WebLogic Server. Finally, we end this section with two detailed examples of building Web Services, one starting from Java and the other starting from WSDL.

WebLogic Server s Web Services Architecture

WebLogic Server provides a Web Services container that allows it to accept SOAP requests, dispatch these requests to the appropriate back-end components , and return SOAP responses. The Web services container architecture, shown in Figure 15.1, is based on the JAX-RPC 1.0 specification. SOAP requests are accepted and handled by the server s HTTP request processing mechanisms and dispatched to the appropriate application, as determined by the URL of the underlying HTTP request (see Chapter 11 for more information regarding this dispatching step). For all SOAP requests , a WebLogic Server-provided servlet known as the WebServiceServlet is the entry point into the application. It is responsible for preparing the request before dispatching it to the back-end Java component, receiving any response, and packaging it into a SOAP response.

click to expand
Figure 15.1:  Web Services container architecture.

The first step in handling the request is to invoke the handleRequest() method of any handlers that have been registered for the target Web Service operation. Assuming that the handlers do not short-circuit the processing, the incoming XML data is then converted into Java objects. WebLogic Server supports mapping between XML and Java for a large number of built-in data types. For data types that are not built in, this mapping is accomplished through the use of serializers .

Serializers, and the accompanying Java classes to represent the data, can be automatically generated for most XML data using WebLogic Server s auto-typing capability. If you want more control, or if the data is too complex for the auto-typing feature, you can also write your own custom serializers and plug them into the Web Services container. For example, custom serializers can be used to map existing WSDL data structures into more convenient Java types for use in your business logic. This is shown in the customer serializer example where we map between a WSDL array of strings and a Java ArrayList .

Once the corresponding Java objects exist, the WebServiceServlet dispatches the request to the back-end component and waits for a response, if applicable . If a response is expected, the entire process is unwound by taking the response and converting the returned Java objects into XML. Again, this is accomplished either automatically for built-in data types or through the use of serializers. The handleResponse() method of any registered handlers is invoked in reverse order, and the resulting SOAP response is returned to the caller.

From a high level, this is really all there is to it. The WebLogic Server JAX-RPC client run time provides a mirror image of the server-side architecture. A Java client invocation is passed to serializers/deserializers, then to the handlers, and finally to the core run time that makes the actual invocation. Responses unwind through the same steps until they are returned to the Java client. Of course, we have skipped over a lot of the details that make writing a Web Services container hard. Fortunately, you don t need to worry about these details because WebLogic Server handles them for you. Now, let s look at the basic mechanisms you use to write your own Web Service.

Developing Web Services with WebLogic Server

When designing a Web Service, the most important thing is to define the right interface. What are the things to consider when designing the interface? The answer to this question will be discussed in various places throughout the rest of the chapter, but suffice it to say that it is something of an art. We will look at a few guidelines to use in defining Web Services interfaces in the Adding Web Services to bigrez.com section at the end of the chapter. For now, we will assume that the interface decisions have been made and now all you need to do is map that into a Web Service running in WebLogic Server.

In this section, we discuss the options at your disposal when building a Web Service. We begin with the types of back-end components that can be exposed as a Web Service, followed by the options for handling data type mapping. We follow this with the basic steps to build a Web Service, starting from either a Java interface or WSDL. This section ends with a discussion of building Web Services clients .

Choosing Back-End Component Types

When starting to implement a Web Service in WebLogic Server, the first thing to consider is what type of back-end component will be used. WebLogic Server 8.1 supports exposing the following types of components as Web Services:

  • Stateless session Enterprise Java Beans

  • Java objects

  • JMS destinations

Determining the type of component to use is not always clear-cut . While we talk about some of the things to consider here, other factors will be discussed throughout the chapter that can affect this decision process.

WebLogic Server can expose the methods on a stateless session bean s remote interface as Web Service operations. Using an EJB gives your Web Service all of the traditional benefits of the EJB container including component-level security, transactions, persistence (when used in conjunction with entity beans), and concurrency control. Because each Web Service invocation is ultimately dispatched to the stateless session bean, you can depend on the semantic guarantees of EJB when writing the Web Service s business logic. The only real drawback in using a stateless session bean is the additional steps necessary to write an EJB versus a regular Java object. Because most Java IDEs support EJB development, we feel that the benefits of using an EJB generally outweigh any extra development effort.

You can expose the public methods on a regular Java class as Web Service operations in a similar manner to exposing the methods of a stateless session bean; however, you lose all of the advantages that the EJB container provides. Your Java class must meet several requirements, the most daunting of which is that your Java code must be thread-safe. The Web Services container creates a single instance of the Java class and dispatches all requests concurrently to that single instance. This does not necessarily impose any performance or scalability limitations. This is the same model used by servlet containers. It simply means that your code must be written with thread-safety in mind. Any synchronization points required to prevent data corruption can affect performance and scalability. The requirements for the Java class are as follows :

  • Do not start any threads (this is not specific to Web Services and is a best practice for all server-side components).

  • Provide a default, no-args constructor.

  • Make public any operations that are to be exposed as Web Services.

  • Write the code in a thread-safe manner.

WebLogic Server also allows you to expose a JMS destination directly as a Web Service. Each of these JMS Web Services allows the caller either to send a message to or receive a message from a JMS destination. This capability gives you some basic mechanisms by which you could build asynchronous Web Services. There are several issues to keep in mind, though, when you are considering this capability.

First, a Web Service that sends a message to a JMS destination must be an asynchronous, one-way operation. This means that the caller receives no indication of whether the server was able to process the message after it is delivered to the JMS destination. This is normal behavior for asynchronous processing. You need to write your application to handle error conditions in both the client and the back-end component processing the message. One way to do this is to write the message-driven bean that is processing the requests to always handle any exceptions and generate the appropriate error messages that will be sent back to the client.

Second, a Web Service call to receive a message does not provide a mechanism for the client to get a specific message. In normal JMS applications, you would use a message correlation ID, a temporary reply-to destination, or similar mechanism so that each client could be assured that it was receiving responses to requests that it sent. A Web Service client that receives a message from a JMS destination will get the first message in the destination. For applications where the only client is another application, the client might be expected to correlate the responses itself if it is sending multiple concurrent requests. Of course, for almost any application scenario that involves multiple processes making concurrent Web Service invocations, this limitation will be a significant issue. The current WebLogic Server Web Service implementation does not provide any support for the client to pass in any information to identify the specific message(s) in which they are interested. Therefore, any Web Services client that is dequeuing messages via a Web Service must be able to process any message in the JMS destination and not just ones associated with some previous request sent by that specific client.

Finally, many production sites will use distributed JMS destinations to allow the application s JMS destinations to be highly available. This works against us when a remote JMS client tries to dequeue a specific message from a distributed destination. The problem comes down to the fact that the caller will be associated with one of the physical member queues when making the dequeue request, but the member queue chosen by WebLogic JMS may not, in fact, be the one that contains the messages that the caller is trying to find. This means that the Web Service used to retrieve a JMS-based Web Service response must be associated with a physical destination in order to make sure that all of the responses are visible. While this is not a show-stopper, it does mean that you must use extra care in designing a Web Sservice-based application that retrieves JMS messages to guarantee the scalability, availability, and fault tolerance of the application.

As you will see in the Adding Web Services to bigrez.com section, we have chosen not to use JMS destinations directly as the back-end components in our bigrez.com site. We could have easily used them to accept incoming requests and used another mechanism to return the responses. See the Adding Web Services to bigrez.com section of this chapter for more information about why we have not used JMS destinations as Web services endpoints.

Handling Data Types

WebLogic Server provides mapping support between Java and XML for a large number of data types, including all of the built-in types defined by the JAX-RPC specification. For these data types, WebLogic Server automatically converts between Java and XML. For all other data types, custom serializer classes are used to perform the mapping. Using these non-built-in data types requires that you perform the following steps:

  1. Write an XML schema representation of your data type.

  2. Write the Java class that represents your data type.

  3. Write the Java serializer class to handle the bi-directional conversion between XML and Java using the WebLogic XML Streaming API.

  4. Update the web-services .xml with the data type information.

We will go through the details of these steps later in the chapter. You may never need to create serializers manually. WebLogic Server provides an auto-typing mechanism that will create serializers automatically via introspection of the Java classes that represent the data type. Through this auto-typing mechanism, WebLogic Server can perform the four steps listed here for virtually any Java object that can be represented using XML schema. This auto-typing feature is accessed through either the servicegen or autotype Ant tasks , which we will be using in our examples throughout the rest of this chapter.

Starting with Java

The most straightforward way to create a Web Service with WebLogic Server is to start by creating the Java component or EJB that implements the Web Service s functionality. Although the steps required to create the Web Service vary depending on the type, the main steps you go through to create your Web Service are as follows:

  1. Write, compile, and package the Java code for the back-end components that provide the business logic for the Web Service.

  2. For back-end components that use non-built-in data types, create the serialization classes required to convert the data between the Java and XML representations.

  3. Create the web-services.xml deployment descriptor that describes the Web Service s deployment characteristics.

  4. If the Web Service s clients will use the WebLogic Server Web Services client to access the Web Service, create the client jar file for the Web Service.

  5. Package everything into a deployable ear file.

Fortunately, WebLogic Server provides a set of Ant tasks that can be used to automate steps 2 through 5. Typically, you will simply use the servicegen task that performs all of these steps. In some cases, it may be necessary to have more control over the process. For example, you may need to modify the deployment descriptor to modify the Web Service s functionality. In these types of scenarios, WebLogic Server provides four single-purpose Ant tasks that can be used to perform each step individually. The tasks are autotype , source2wsdd , clientgen , and wspackage , and each one performs one of the four steps (that is, steps 2 through 5, respectively) required to turn your back-end component into a deployable Web Service. Shortly, we will walk through an example that illustrates the use of servicegen . As we start building more complex examples, we will make use of the individual Ant tasks that make up the servicegen task.

Starting with WSDL

Occasionally, you need to create a Web Service that complies with a predefined interface written in WSDL. This requirement can arise for many reasons. It may be something as simple as wanting to upgrade or replace an existing application without breaking its interface contract. As we will see later in our bigrez.com example, it can also be the result of wanting to integrate with an application that implements asynchronous Web Services. To create a Web Service starting with WSDL, the main steps you go through are as follows:

  1. For a WSDL description of a Web Service that uses non-built-in types, create the Java versions of those types and the serialization classes used to convert between Java and XML representations.

  2. Create the back-end components that implement the operations defined by the WSDL description.

  3. Create the web-services.xml deployment descriptor.

  4. If the Web Service s clients will use the WebLogic Server Web Services client to access the Web Service, create the client jar file for the Web Service.

  5. Package everything into a deployable ear file.

Again, WebLogic Server provides a set of Ant tasks that can be used to automate most of the process. The autotype task mentioned previously generates the Java versions of WSDL-defined types, the serialization classes, and an XML description of the types that will be used to create the type mapping information in the web-services.xml deployment descriptor. Once you have the type information, the wsdl2service task generates both a Java interface that represents the Java implementation of the Web Service operations defined by the WSDL and the web-services.xml deployment descriptor. If you want to include a downloadable client jar file, use the clientgen task to create the jar file from the WSDL. Once you have implemented the Java class that implements the generated interface, the wspackage task creates the deployable ear file. We will look at an example of how to do this shortly.

Writing Web Services Clients

When writing J2EE applications, you often need to access other back-end services (for example, a database or credit card authorization service) to access application data or functionality. With the introduction of Web Services, you may find that some of the back-end services you need to access are available only via a Web Service interface. You may also find that you need to write Java clients to access your WebLogic Server-hosted Web Services. Fortunately, WebLogic Server makes both of these jobs possible using its implementation of the JAX-RPC client run time.

With JAX-RPC, there are three different ways to write a client. Static clients are the simplest in that they provide a strongly typed Java interface to access the Web Service functionality. WebLogic Server provides the clientgen Ant task to generate the Web Service-specific classes needed to invoke a Web Service statically. Using either the WSDL or the WebLogic Server deployable Web Service ear file as input, clientgen creates a jar file containing the JAX-RPC Service implementation and the service-specific stub that a Java client will use to invoke the Web Service. The next method involves using dynamic proxies to invoke the Web Service. With dynamic proxies, no generated stub class is required, but you still get a strongly typed stub by generating a dynamic proxy that matches the interface of the service endpoint. JAX-RPC also provides a third mechanism for invoking Web Services that is similar to the Java Refection APIs for invoking Java methods: dynamic clients . When writing dynamic clients, you don t need to use clientgen because no Web Service-specific classes are needed by the client.

WebLogic Server 8.1 provides three different JAX-RPC client run-time jar files that you can use to invoke Web Services hosted in any SOAP-compatible container; the one you need depends on your client, as shown here:

webserviceclient.jar.     This jar file contains the WebLogic Server implementation of the JAX-RPC run time.

webserviceclient+ssl.jar.     This jar file contains the WebLogic Server implementation of the JAX-RPC run time and SSL libraries.

webserviceclient+ssl_pj.jar.    This jar file contains the WebLogic Server implementation of the JAX-RPC run time and SSL libraries for the CDC profile of J2ME.

One of the big advantages of using Web Services is that the client and Web Service implementation do not need to be using the same vendor s run times, or even the same programming language. We will provide examples of how to create and run both Java and Microsoft VisualBasic.NET clients later in this chapter.

Creating a Web Service Starting with Java

For the purposes of our initial discussions, we focus on simple Web Services that allow you to concentrate on the mechanics of building Web Services rather than the details of the business logic. Rest assured that the bigrez.com Web Services in the final section provides enough real-world complexity to understand the capabilities of WebLogic Server s Web Services support.

Suppose that we want to write a Web Service that supports searching for hotels by location. Let s start out by building a stand-alone Web Services application to do this. The complete example can be found in the Chapter 15 examples on the companion Web site (http://www. wiley .com/compbooks/masteringweblogic). Our Web Service will access the bigrez.com database directly rather than modifying the existing application to access it through our entity beans. The first step is to define the input and output data. We want to allow searching by city and/or state, or by zip code. The results will contain zero or more objects that contain information about each matching hotel. The Java objects that we use to represent the results are shown in Listings 15.1 and 15.2. Notice that each object is serializable and contains a default, no-args constructor and get and set methods for each attribute. This is required if you choose to use the servicegen (or autotype ) Ant task to create your serializer classes automatically.

Listing 15.1:  PropertyInfo.java.
start example
 package mastering.weblogic.ch15.example1; public class PropertyInfo implements java.io.Serializable {     private int id;     private String description;     private String features;     private String address1;     private String address2;     private String city;     private String state;     private String postalCode;     private String phone;     public PropertyInfo() { }     public PropertyInfo(int id, String description, String features,                         String address1, String address2, String city,                         String state, String postalCode, String phone) {         this.id = id;         this.description = description;         this.features = features;         this.address1 = address1;         this.address2 = address2;         this.city = city;         this.state = state;         this.postalCode = postalCode;         this.phone = phone;     }     public int getId() { return id; }     public void setId(int id) { this.id = id; }     public String getDescription() { return description; }     public void setDescription(String description)         { this.description = description; }     public String getFeatures() { return features; }     public void setFeatures(String features)          { this.features = features; }     public String getAddress1() { return address1; }     public void setAddress1(String address1)         { this.address1 = address1; }     public String getAddress2() { return address2; }     public void setAddress2(String address2)         { this.address2 = address2; }     public String getCity() { return city; }     public void setCity(String city) { this.city = city; }     public String getState() { return state; }     public void setState(String state) { this.state = state; }     public String getPostalCode() { return postalCode; }     public void setPostalCode(String postalCode)         { this.postalCode = postalCode; }     public String getPhone() { return phone; }     public void setPhone(String phone) { this.phone = phone; } } package mastering.weblogic.ch15.example1; public class PropertySearchResults implements java.io.Serializable {     private String city;     private String state;     private String zip;     private PropertyInfo[] properties;     public PropertySearchResults() { }     public PropertySearchResults(String city, String state,                                  String zip, PropertyInfo[] properties)     {         this.city = city;         this.state = state;         this.zip = zip;         this.properties = properties;     }     public String getCity() { return city; }     public void setCity(String city) { this.city = city; }     public String getState() { return state; }     public void setState(String state) { this.state = state; }     public String getZip() { return zip; }     public void setZip(String zip) { this.zip = zip; }     public PropertyInfo[] getProperties() { return properties; }     public void setProperties(PropertyInfo[] properties)         { this.properties = properties; } } 
end example
 

Next, we will create a simple Java class to process the request. Our PropertySearchServiceImpl class, shown in Listing 15.3, exposes two public methods, findByCityState() and findByZip() , that will be turned into operations on our Web Service. The business logic is simplistic in that it looks for exact matches and ignores exceptions. Notice that we have been extra careful to make sure that the class is thread-safe ”this is an important requirement for any regular Java class that you want to expose as a Web Service. If you would rather not worry about thread-safety, then you need to use an EJB instead.

Listing 15.3:  PropertySearch.java.
start example
 package mastering.weblogic.ch15.example1; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; public class PropertySearchServiceImpl {     private static final String DS_JNDI_NAME = "BigRezDataSource";     private static final String CITY_STATE_SQL =         "SELECT id, description, features, address1, address2, city, " +         "statecode, postalcode, phone FROM property WHERE city = ? " +         "AND statecode = ?";     private static final String ZIP_SQL =         "SELECT id, description, features, address1, address2, city, " +         "statecode, postalcode, phone FROM property WHERE " +          "postalcode = ?";     private DataSource dataSource;     public PropertySearchServiceImpl()     {         getDataSource(); // try to pre-fetch the DataSource from JNDI     }     public PropertySearchResults findByCityState(String city,                                                  String state)     {         PropertySearchResults results = new PropertySearchResults();         results.setCity(city);         results.setState(state);         Connection conn = null;         PreparedStatement ps = null;         ResultSet rs = null;         try {             conn = getDataSource().getConnection();             ps = conn.prepareStatement(CITY_STATE_SQL);             ps.setString(1, city);             ps.setString(2, state);             rs = ps.executeQuery();             results.setProperties(processResults(rs));         }         catch (Exception ignore) {             ignore.printStackTrace();             // Show how to handle expections later...         }         finally { closeDatabaseResources(conn, ps, rs); }         return results;     }     public PropertySearchResults findByZip(String zip)     {         PropertySearchResults results = new PropertySearchResults();         results.setZip(zip);         Connection conn = null;         PreparedStatement ps = null;         ResultSet rs = null;         try {             conn = getDataSource().getConnection();             ps = conn.prepareStatement(ZIP_SQL);             ps.setString(1, zip);             rs = ps.executeQuery();             results.setProperties(processResults(rs));         }         catch (Exception ignore) {             ignore.printStackTrace();             // Show how to handle exceptions later...         }         finally { closeDatabaseResources(conn, ps, rs); }         return results;     }     private DataSource getDataSource()     {         // Instead of using locks, we accept the fact that multiple         // threads might do the lookup concurrently. This is okay since         // all threads will set dataSource to an equivalent value. The         // first call should be from the constructor so dataSource         // should already be set before multiple threads start invoking         // this method.         //         if (dataSource == null) {             try {                 InitialContext ctx = new InitialContext();                 dataSource = (DataSource)ctx.lookup(DS_JNDI_NAME);             }             catch (NamingException ignore) {  }         }         return dataSource;     }      private PropertyInfo[] processResults(ResultSet rs)          throws SQLException     {         java.util.ArrayList list = new java.util.ArrayList();         while (rs.next()) {             PropertyInfo property =                 new PropertyInfo(rs.getInt(1), rs.getString(2),                                  rs.getString(3), rs.getString(4),                                  rs.getString(5), rs.getString(6),                                  rs.getString(7), rs.getString(8),                                  rs.getString(9));                 list.add(property);         }         int len = list.size();         PropertyInfo[] properties = new PropertyInfo[len];         for (int i = 0; i < len; i++)             properties[i] = (PropertyInfo)list.get(i);         return properties;     }     private void closeDatabaseResources(Connection conn,                                         PreparedStatement ps,                                         ResultSet rs)     {         if (rs != null)              try { rs.close(); } catch(SQLException ignore) { }         if (ps != null)              try { ps.close(); } catch(SQLException ignore) { }         if (conn != null)              try { conn.close(); } catch(SQLException ignore) { }     } } 
end example
 

The only thing left to do is to build and deploy the Web Service. After compiling our three classes, we will use the WebLogic Server-provided servicegen Ant task to generate our ear file. Let s take a look at the servicegen task definition located in the compile_example1 task in this chapter s build.xml file:

 <property name="src" value="${basedir}/src"/> <property name="build" value="${basedir}/build"/> <property name="pkg_base" value="mastering.weblogic.ch15.example"/> <property name="svc_name" value="PropertySearchService"/> ... <servicegen classpathref="dev.class.path" destEar="ch15_example1.ear"             contextURI="/ch15_example1">   <service javaClassComponents="${pkg_base}1.${svc_name}Impl"            targetNamespace="http://www.bigrez.com/ch15/example1/"            serviceName="${svc_name}" serviceURI="/${svc_name}">     <client useServerTypes="true" packageName="${pkg_base}1"/>   </service> </servicegen> 

Using this task definition, servicegen will create a Web Service called Property-SearchService that will be accessible through the URI /ch15_example1/PropertySearchService on whatever server we deploy it. In addition to creating our Web Service, WebLogic Server provides a dynamically generated home page for the Web Service, the dynamically generated WSDL describing the Web Service, and a downloadable client jar file containing the stubs for statically invoking the Web Service from Java using the JAX-RPC programming model. The home page is accessible using the URL of the Web Service, which is a combination of the ContextURI and the ServiceURI in the servicegen task (for example, http://localhost:7001/ch15_examples/PropertySearchService). To access the WSDL, simply append the parameter WSDL to the home page URL (for example, http://localhost:7001/ch15_examples/PropertySearchService?WSDL). Before we move on, let s take a minute to look under the covers at exactly what servicegen did for us.

First, servicegen examined our public methods in our PropertySearch_ServiceImpl class to determine the data types of the arguments and return types. Seeing that both methods use built-in types for arguments, servicegen had to be concerned only with the PropertySearchResults type being used by both operations as their return values. Because PropertySearchResults also contains an array of the nonbuilt-in type PropertyInfo , servicegen created the XML schema description of three types for us: PropertyInfo , PropertyInfo[] , and Property_SearchResults . In addition, servicegen also created a serializer and holder class for each of the three nonbuilt-in types. The serializer classes, with names like PropertyInfoCodec , handle the conversion of the data between Java and XML. Holder classes are the classes defined by JAX-RPC that you use to handle Web Services that use in-out and out parameters as arguments in order to return multiple results. Holder classes are not needed in our current example.

Next, servicegen generated our web-services.xml deployment descriptor (which is packaged as part of the Web application contained in the generated ear file). In many situations, you never have to worry about what is contained in the Web Services deployment descriptor, so we will not spend time looking at it just yet. Suffice it to say that it contains all of the information about the types, the operations, the Web Services, and their URIs for WebLogic Server to be able to deploy the Web Services and generate their WSDL dynamically.

Optionally, servicegen can generate a client jar file that contains the Web Service-specific classes needed to invoke the Web Service statically from a client using the JAX-RPC programming model. This jar file, in conjunction with one of the three JAX-RPC client run-time jar files we discussed previously, is all you need on the client. Of course, nothing says that you have to use these classes to invoke a Web Service. Numerous other tools and servers support Web services. These are provided for your convenience should you choose to use them. Shortly, we will use these jar files to write a simple client to test the Web Service we just wrote.

Finally, servicegen creates the Web application and enterprise application deployment descriptors and packages everything up into a deployable ear file. This step is really just a convenience for situations where your Web Service does not include a bunch of other auxiliary files that need to be packaged with it. As we will see later, many times the limitations of servicegen in packaging capabilities make it necessary to resort to the WebLogic Server-provided wspackage Ant task, or even assembling the pieces manually using the built-in war and ear tasks provided by Ant. Fortunately, we already know all we need to know about automating the creation of Web and enterprise applications, so this really isn t a problem.

Before we move on to discuss creating a Web service to match an existing WSDL description of it, let s test our Web service. Because our Web service is relatively simple, we can use the test pages that WebLogic Server generates for us, as shown in Figure 15.2. The home page is accessible through a URL of the form http:// < hostname > : < port > / < contextURI > / < serviceURI >. Because we set the contextURI to /ch15_example1 and the ServiceURI to /PropertySearchService in our servicegen task, the URL of our home page is http://localhost:7001/ch15_example1/PropertySearchService. If you forget the URL or are having trouble accessing your Web Service home page, you can find a link to it on the Testing tab for the Web application containing your Web Service (for example, Deployments- > Applications- > ch15_example1- > /ch15_example1 in the left-hand navigation bar) in the WebLogic Console.

click to expand
Figure 15.2:  Viewing the Web Service home page.

From this page, selecting the findByZip link will take you to the input screen for our findByZip() operation. If we type in the zip code 55401 and invoke the Web Service, we will get back a results page similar to the one shown in Figure 15.3. As you can see, the test results page shows that the service found one hotel for zip code 55401 . Fortunately, most hotel Web sites don t search on zip codes by exact match. Now, let s look at writing a Web Service starting from WSDL.

click to expand
Figure 15.3:  Testing Web Service operations.

Creating a Web Service Starting with WSDL

Suppose that we have an important potential partner organization that has applications already written to consume a property search Web Service from one of our competitors . As part of a big business deal to convince them to switch to our hotels, the CEO has agreed to modify our Web Service to match the WSDL of our competitor. In the interest of conserving space, we have chosen a WSDL document that is compatible with our Web Service implementation from the previous section. That way, we can spend our time looking at the mechanics rather than the details of the business logic. We ll begin with the PropertySearchService.wsdl file for this example, available in the downloadable code at http://www.wiley.com/compbooks/masteringweblogic.

WebLogic Server provides the wsdl2service Ant task that can take a WSDL docu-ment and produce the corresponding Java interface. Once we have the interface, all we have to do is implement it, package it, and deploy it. So, let s get started. Because our WSDL defines some non-built-in data types, the first thing we need to do is to generate the type information that wsdl2service requires. Fortunately, the autotype Ant task is specifically designed for this purpose. For each type defined in the WSDL document, autotype can generate the Java class representing that type, the serialization and holder classes, and the type definition and mapping information we need in the web-services.xml deployment descriptor.

Let s take a look at the autotype task definition that generates the type information:

 <property name=src value=${basedir}/src/> <property name=types value=${basedir}/types/>   <property name=pkg_base value=mastering.weblogic.ch15.example/> <property name=pkg_base_dir value=mastering/weblogic/ch15/example/> <property name=example2_wsdl           value=${src}/${pkg_base_dir}2/PropertySearchService.wsdl/> ... <autotype classpathref=dev.class.path wsdl=${example2_wsdl}           targetNamespace=http://www.bigrez.com/ch15/example2/           packageName=${pkg_base}2 destDir=${types}/> 

We simply specify the WSDL file name (or URL), the target namespace for any types created, the package name to use for the generated Java types, and the output directory. The autotype task generates the Java source code classes, the compiled versions of those classes, and a file called types.xml that provides all of the type description and mapping information. Notice that we use a separate directory for the autotype output. The wspackage task that we will use to generate the ear file expects the serializers to be in a separate directory.

Now we are ready to generate the Java interface and Web Service deployment descriptor that represents the Web Service defined in our WSDL file. As you can see, the wsdl2service task definition is similar to the autotype definition; the main difference is that it reads the types.xml file created by autotype to determine the Java data types to use as the arguments and return values of the method definitions it creates:

 <wsdl2service classpathref="dev.class.path" wsdl="${example2_wsdl}"               typeMappingFile="${types}/types.xml"               packageName="${pkg_base}2" destDir="${src}"/> 

The Java interface class produced will have the same name as the Web Service s service element specified in the WSDL. It is important to mention that, when generating the web-services.xml deployment descriptor, wsdl2service assumes that you will be using a Java class to implement the service and that the name of that class will be of the form < packageName > . < serviceName > Impl . If either of these is not true, then you will need to edit the < components > section of the deployment descriptor accordingly . We hope that future versions of the wsdl2service will provide configurable attributes to control these assumptions, thus eliminating the need to edit the deployment descriptor should your implementation not match the assumptions.

We are now ready to implement our Web Service. Because we chose the WSDL to be compatible with our previous example, we simply took the code shown in Listing 15.3 and changed the package name to mastering.weblogic.ch15.example2 . Once that is compiled, we can move on to the next step.

If desired, we can generate the client jar file using the clientgen task. As you might expect, clientgen simply inspects the WSDL and generates all of the Web service-specific classes that a JAX-RPC client would need to invoke the Web Service statically. For completeness, the clientgen task is shown here:

 <clientgen classpathref=dev.class.path wsdl=${example2_wsdl}            packageName=${pkg_base}2            clientJar=${src}/${pkg_base_dir}2/${svc_name}_client.jar/> 

Finally, we are ready to package the different elements of the Web Service implementation into a deployable ear file. WebLogic Server provides the wspackage task for this purpose. Let s look at our Ant script fragment dealing with packaging the Web Service:

 <delete quiet=true>   <fileset dir=${types}>     <include name=**/*.java/>     <include name=types.xml/>   </fileset> </delete> <wspackage classpathref=dev.class.path ddFile=${src}/web-services.xml            contextURI=/ch15_example2 codecDir=${types}            filesToWar=${src}/${pkg_base_dir}2/${svc_name}_client.jar            webAppClasses=${pkg_base}2.${svc_name},                           ${pkg_base}2.${svc_name}Impl            output=ch15_example2.ear/> 

First, we remove all of the Java source files and the types.xml from the types directory hierarchy. We do this because the wspackage task simply takes everything in the directory specified by the codecDir attribute and adds it to the Web application s WEB-INF/classes directory; this is why we chose to use a separate output directory for the autotype task. The wspackage task provides arguments that allow you to insert files almost anywhere you want in the enterprise and Web application hierarchy. For example, webAppClasses specifies other classes to place in the WEB-INF/classes directory while filesToWar specifies the files to place in the root directory of the Web application. The wspackage task should satisfy many of your Web Service application packaging needs; however, we will see in later examples that certain packaging requirements require assembling the application manually.

Once we deploy our packaged application, we can once again use the WebLogic Server-provided testing pages to verify that our Web Service is indeed functioning correctly. Now, let s spend a little time looking at how to write code that invokes a Web Service.

Creating Web Service Clients with WebLogic Server

In this section, we will briefly look at how to write a Web Service client. As we mentioned previously, JAX-RPC provides three different client-side programming models for invoking Web Services: the strongly typed, static-invocation model using generated stubs, dynamic proxies, and the loosely typed, dynamic-invocation model. In addition, WebLogic Server adds some client-side functionality that extends the JAX-RPC specification to allow the construction of more robust enterprise Web Service applications through asynchronous invocation and portable stubs. We will examine each of these mechanisms in the context of invoking a Web Service. We ll review each of the three models before presenting our recommendations for their use.

Rather than writing a client for a WebLogic Server-hosted Web Service, we are going to make use of a sample Web Service to calculate the distance between two zip codes. This Zip Distance Calculator Web Service is not intended for commercial purposes and is hosted by Imacination Software (http://www.imacination.com). The home page for this Web Service is available at http://webservices.imacination.com/distance/, and the WSDL URL is http:// webservices .imacination.com/distance/Distance.jws?wsdl .

JAX-RPC defines four primary object types that clients use to invoke Web Services: ServiceFactory , Service , Stub , and Call . All of these objects are part of the javax.xml.rpc package. Which objects are used and whether the client application uses the objects directly or indirectly depend on which programming model the client uses. The list that follows summarizes the purpose each object serves in the JAX-RPC model:

ServiceFactory.     This object is the factory through which clients can create an instance of the Service object without having to use the constructor of the Service implementation class.

Service.     This object represents the Web Service and acts as a factory for the Stub and Call objects.

Stub.     This object represents the service endpoint (also known as the port) on which any operations will be invoked.

Call.     This object represents an invocation of an operation that is performed using the dynamic invocation model.

Using Generated Stubs

The JAX-RPC static invocation model uses generated classes that implement the Service and Stub objects and expose the operations of the Web Service endpoint as Java method calls. Using the clientgen Ant task, we generate our Zip Distance Calculator Web Service client jar file using the WSDL as input. If you look inside the generated jar file, you will see the file structure shown in Figure 15.4 (the listing omits the Java source files that are also included by default).

click to expand
Figure 15.4:  Client-specific jar file contents.

WebLogic Server has generated everything we need to invoke the operations on the service endpoint. If the Web Service had defined nonbuilt-in types, clientgen would have generated the Java implementation, the serializer, and the holder classes, and it would have put the mapping information into the DistanceServices.xml file that is used by WebLogic Server s implementation of the client-side JAX-RPC run time. If we look at the Distance interface, we see six methods that correspond to the six operations defined in the WSDL. To invoke one of those methods, all we need to do is create the Service object, get the service endpoint, and invoke the operation, as shown:

 private static final String WSDL_LOCATION =     http://webservices.imacination.com/distance/Distance.jws?wsdl; ... DistanceService service = new DistanceService_Impl(WSDL_LOCATION); Distance port = service.getDistance(); double distance = port.getDistance(zip1, zip2); 

The complete program is available as part of Chapter 15 s examples on the companion Web site (http://www.wiley.com/compbooks/masteringweblogic).

As a convenience and a performance booster, clientgen packages a copy of the WSDL in the client jar file. To take advantage of this, simply instantiate the new DistanceService_Impl instance using the no-args constructor. If you were to look at the generated Java code, you would see that this causes the instance to use the WSDL file included in the client jar file rather than making a separate call to the server to get the WSDL before it invokes the service.

Best Practice  

When using generated stubs, use the no-args constructor to create the service object to prevent an extra call to the server to retrieve the WSDL.

Using Dynamic Proxies

Dynamic proxies are used to write a Web Service client that is independent of the underlying JAX-RPC run time while still providing a strongly typed interface for the service endpoint. To use this model, you will still need to have a Java interface that accurately represents the Web Service endpoint operations. You can either write this interface yourself using the JAX-RPC WSDL to Java mapping specification or use a tool to generate it for you. In our example, we use the Distance interface generated by clientgen in the previous section.

When writing a client that uses dynamic proxies, the first thing you need to do is create the Service object. Typically, you do this by using the ServiceFactory object. Because javax.xml.rpc.ServiceFactory is an abstract class, you must set a Java system property that specifies the name of your JAX-RPC run time s ServiceFactory implementation class. The name of this system property is javax.xml.rpc.ServiceFactory; the ServiceFactory class has a static attribute named SERVICEFACTORY_PROPERTY that contains this value. Once you have set this property, use the newInstance() and createService() methods to create the Service object. Now that you have the Service object, it is simply a matter of creating the dynamic proxy for the service endpoint by specifying the port name and the interface class that the dynamic proxy should implement. These code fragments from our next example illustrate the important steps in the process:

 import java.net.URL; import javax.xml.namespace.QName; import javax.xml.rpc.Service; import javax.xml.rpc.ServiceFactory; ... private static final String WSDL_LOCATION =     "http://webservices.imacination.com/distance/Distance.jws?wsdl"; private static final String TARGET_NAMESPACE =     "http://webservices.imacination.com/distance/Distance.jws"; private static final String SERVICE_NAME = "DistanceService"; private static final String PORT_NAME = "Distance"; ... System.setProperty(ServiceFactory.SERVICEFACTORY_PROPERTY,                    "weblogic.webservice.core.rpc.ServiceFactoryImpl"); ServiceFactory factory = ServiceFactory.newInstance(); QName serviceName = new QName(TARGET_NAMESPACE, SERVICE_NAME); URL wsdlLocation = new URL(WSDL_LOCATION); Service service = factory.createService(wsdlLocation, serviceName); QName portName = new QName(TARGET_NAMESPACE, PORT_NAME); Distance port = (Distance)service.getPort(portName, Distance.class); double distance = port.getDistance(zip1, zip2); 

When you have nonbuilt-in data types, you still need Java classes that represent the nonbuilt-in types and serializer classes that convert between their XML and Java representations. Because the Service object is dynamically created, you need to register all of the type mapping information using the TypeMappingRegistry object associated with the Service object.

All of this can theoretically be done in a JAX-RPC client run-time-independent way if you are willing to do all the necessary work. Unfortunately, standardization of the serialization framework is not covered by the JAX-RPC 1.0 specification. In addition, each run-time implementation tends to use a specific type of parser for processing the XML “ WebLogic Server s run time uses a streaming parser. This means that your deserialization classes would need to be able to handle the type of parser used by the particular run-time implementation.

Warning  

Because the JAX-RPC 1.0 specification does not standardize a serialization framework to convert between Java and XML, any dynamic proxy or dynamic invocation clients that invoke Web Services that require the use of nonbuilt-in Java data types will be dependent on the underlying JAX-RPC client run-time implementation.

Using Dynamic Invocation

Dynamic invocation takes the dynamic proxy model a step further by removing the need to have a predefined interface that represents the Web Service endpoint. With this model, the Call object replaces the strongly typed service endpoint. It is the client application programmer s responsibility to configure the Call object properly and to supply the correct arguments when invoking the operation. The code fragment that follows shows the important elements of our dynamic invocation client for the Zip Distance Calculator Web Service:

 import java.net.URL; import javax.xml.namespace.QName; import javax.xml.rpc.Call; import javax.xml.rpc.Service; import javax.xml.rpc.ServiceFactory; ... private static final String WSDL_LOCATION =     http://webservices.imacination.com/distance/Distance.jws?wsdl; private static final String TARGET_NAMESPACE =     http://webservices.imacination.com/distance/Distance.jws; private static final String SERVICE_NAME = DistanceService; private static final String PORT_NAME = Distance; private static final String OPERATION_NAME = getDistance; ... System.setProperty(ServiceFactory.SERVICEFACTORY_PROPERTY,                    weblogic.webservice.core.rpc.ServiceFactoryImpl); ServiceFactory factory = ServiceFactory.newInstance(); QName serviceName = new QName(TARGET_NAMESPACE, SERVICE_NAME); URL wsdlLocation = new URL(WSDL_LOCATION); Service service = factory.createService(wsdlLocation, serviceName); QName portName = new QName(TARGET_NAMESPACE, PORT_NAME); QName operationName = new QName(TARGET_NAMESPACE, OPERATION_NAME); Call call = service.createCall(portName, operationName); Object[] opArgs = new Object[] {zip1, zip2}; Double distance = (Double)call.invoke(opArgs); 

Like the dynamic proxy model, things get more complex when you have nonbuilt-in data types. Because the typical use of the dynamic invocation model is to invoke Web Services whose interfaces are not known at compile-time, any complex XML data types will need to be mapped to existing application classes and will require serialization classes capable of mapping the XML data to Java representations. As in the dynamic proxy case, you will need to register all of the type mapping information before invoking the Web Service operation. Because the serialization framework and XML parser interface to be used by it are not yet defined by the JAX-RPC 1.0 specification, any client that requires support for complex data types will not be independent of the JAX-RPC client run time.

Best Practice  

Use generated stubs whenever the Web Services your client is invoking are known at compile time and your client does not need to be independent of the JAX-RPC client run-time implementation. Use dynamic proxies whenever the Web Services your client is invoking are known at compile-time but you want your client to be able to use multiple JAX-RPC client run-time implementations . Use dynamic invocation only when your client needs to discover and invoke Web services dynamically at run time. Remember that the need to support complex XML data types will tie your client to a particular JAX-RPC 1.0 client run-time implementation; therefore, it is often simpler just to package up the JAX-RPC client run time with your application rather than trying to use dynamic proxies or the dynamic invocation model.

Using WebLogic Server s Asynchronous Invocation

The JAX-RPC client model supports both the synchronous request/response and one-way invocation models. WebLogic Server 8.1 extends the JAX-RPC model to support an asynchronous request/response model in a way that is completely independent of the server-side Web Services container. As a result, the WebLogic Server JAX-RPC client run time can be used asynchronously to invoke any request/response style Web Service operations.

To use this feature, the first thing that we need to do is generate the Web Service-specific client jar file using the clientgen Ant task with the generateAsync-Methods attribute set to true :

 <clientgen classpathref="client.compile.class.path"            wsdl="${example3_wsdl}" packageName="${pkg_base}3"  generateAsyncMethods="true"  clientJar="DistanceService_client.jar"/> 

Once again, the complete source code is available on the companion site for your review. This flag causes clientgen to generate two extra methods per operation on the Web Service endpoint interface and stub; one to send the Web Service request to the server, the other to get the response. Let s look at an example.

In our Zip Distance Calculator Web Service, our generated stub client invokes the getDistance operation whose method declaration looks like this:

 public double getDistance(String fromZip, String toZip)     throws java.rmi.RemoteException; 

When the generateAsyncMethods attribute is true , clientgen also produces the following two methods that we can use to invoke the getDistance operation asynchronously:

 import weblogic.webservice.async.AsyncInfo; import weblogic.webservice.async.FutureResult; ... public FutureResult startGetDistance(String fromZip, String toZip,                                      AsyncInfo asyncInfo)     throws java.rmi.RemoteException; public double endGetDistance(FutureResult _futureResult)     throws java.rmi.RemoteException; 

Now that we understand the effect of generateAsyncMethods , let s look at how to use these methods to invoke the getDistance operation asynchronously.

To invoke the getDistance operation asynchronously, simply use the startGetDistance() method shown previously. There are a couple of different ways to obtain the response. First, we can simply call the endGetDistance() method:

 FutureResult result = port.startGetDistance(zip1, zip2, null); double distance = port.endGetDistance(result); 

This method will return the result, blocking if the result is not immediately available. While this is useful, many times we would prefer not to call endGetDistance() until we are sure that the result is available so that the call will not block. Fortunately, the FutureResult object has the isCompleted() method that allows us to determine if the call has returned and the results are available. The code fragment that follows shows a simplistic way of using this approach:

 FutureResult result = port.startGetDistance(zip1, zip2, null); while (!result.isCompleted()) {     try { Thread.sleep(100); } catch (InterruptedException ignore) { } } double distance = port.endGetDistance(result); 

The other approach is to register a listener that gets called back whenever the result is available. To do this, you need to create an AsyncInfo object on which you set a ResultListener that is to be called when the invocation returns:

 import weblogic.webservice.async.AsyncInfo; import weblogic.webservice.async.FutureResult; import weblogic.webservice.async.InvokeCompletedEvent; import weblogic.webservice.async.ResultListener; ... AsyncInfo asyncInfo = new AsyncInfo(); asyncInfo.setResultListener(new ResultListener() {     public void onCompletion(InvokeCompletedEvent event)     {         Distance port = (Distance)event.getSource();         FutureResult result = event.getFutureResult();         try {             double distance = port.endGetDistance(result);             System.out.println(The result is  + distance +  miles.);         }         catch (RemoteException re) {             re.printStackTrace();         }     } }); FutureResult result = port.startGetDistance(zip1, zip2, asyncInfo); 

Once the call to the getDistance operation returns, the WebLogic Server JAX-RPC client run time will invoke the onCompletion() method on your ResultListener . Using the InvokeCompletedEvent object, you can retrieve the Web Service endpoint and FutureResult object, as shown previously.

It is important to understand that your callback object will be invoked from a different thread than the one that executed the code shown previously. You are responsible for passing information between the two threads. More importantly, the thread that started the call will continue to run regardless of when the callback occurs. For client applications, this is usually the desired behavior because the invoking thread may very well be the event thread of a client s graphical user interface (GUI). This behavior, however, can create unnecessary complexity for server-side applications that are waiting on the result in order to respond to a user request. Let s look at a quick example.

Imagine an application that uses an EJB that is responding to a user request by calling out to a Web Service to access some back-end data. Invoking the Web Service asynchronously allows the EJB to do other work while waiting for the result, but at some point the EJB will need to wait on the result before returning its response to the client. How do you as the EJB programmer prevent the EJB method from finishing before the result returns? How do you determine when the callback has been invoked? Remember, using thread-control primitives is strictly prohibited by the EJB specification and a bad practice even if the EJB container does not prevent their use. Having the ResultListener call back into the EJB will create a reentrant call to the EJB that is prohibited by the EJB specification for session beans and highly discouraged for entity beans. Of course, you could always poll the ResultListener object. Why would you do this when this is exactly the same thing that you can do with the Future-Result.isCompleted() method? Of course, the answer is you wouldn t. If your EJB finishes all of its other work, you would probably just call endGetDistance() to block and wait for the results.

Best Practice  

When calling a Web Service from within a synchronously invoked J2EE server-side component such as an EJB, prefer the explicit testing method of waiting for the results rather than using a ResultListener to avoid the complexities of coordinating multiple threads.

Using Portable Stubs

WebLogic Server s portable stubs feature is not about generating stubs that are independent of the JAX-RPC client run time. Portable stubs allow you to create Web Service clients that can run in any version of WebLogic Server without worrying about internal class name conflicts that might otherwise occur. For example, suppose that you want to use the new asynchronous client invocation capabilities in an application running on WebLogic Server 7.0, which doesn t support this new feature. As you can imagine, some of the JAX-RPC server-side run-time classes for 7.0 and client-side run-time classes for 8.1 share the same names but have different implementations. If you tried to package up the 8.1 webserviceclient.jar in your 7.0 application, you are pretty much guaranteed that it will fail because of the wrong class being loaded. Portable stubs help you solve this problem by renaming all of the WebLogic Server classes to make their names version-specific. We recommend using portable stubs with any server-side application that acts as a Web Service client.

To support the concept of portable stubs, WebLogic Server provides an alternate version of the JAX-RPC client run time. For WebLogic Server 8.1, the alternate version of the JAX-RPC run time is contained in the wsclient81.jar file and simply renames all of the classes in the weblogic.* packages to use weblogic81.* package names. This prevents the client-side run-time classes from conflicting with any server-side classes of the same name. Typically, you would package the wsclient81.jar file with your application, as you would for any other application-specific jar file, rather than adding it to your server s classpath.

In addition, WebLogic Server provides the VersionMaker utility that can take the Web Service-specific client jar file produced by clientgen and convert every class and reference from using the weblogic.* packages to the weblogic81.* package names. To use VersionMaker , you simply point it at the set of jar files that need to be converted and tell it the directory to which to write the modified classes, as shown here:

 java weblogic.webservice.tools.versioning.VersionMaker output_directory      DistanceService_client.jar 

In general, we recommend that you always use portable stubs when building any server-side application that makes calls out to Web Services to reduce the coupling between your application and the specific version of WebLogic Server to which you deploy the application. The web services clients example on the companion Web site contains a working example that uses portable stubs.

Best Practice  

Always use portable stubs for any server-side application that makes calls to Web Services to reduce the dependency of your application on a particular version of WebLogic Server. Always package the alternate JAX-RPC client run-time jar file as part of your application rather than placing it in the server s classpath.




Mastering BEA WebLogic Server. Best Practices for Building and Deploying J2EE Applications
Mastering BEA WebLogic Server: Best Practices for Building and Deploying J2EE Applications
ISBN: 047128128X
EAN: 2147483647
Year: 2003
Pages: 125

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net