Chapter 30. Developing Web Services for WebLogic Server

Introduction to Web Service Development

Chapter 29, “Web Services and the WebLogic Platform,” presented an overview of the Web services supported by WebLogic Server 7.0 (WLS). This chapter describes how WLS Web services work. First, it reviews exactly what thought processes and design issues are involved when you are conceiving a Web service. Then it delves into the tools, deployment, descriptor elements, and features of Web services and why you might choose one tool or feature over another. Then, after you develop your Web service, you need to think about why or how you might need to protect or secure its access.

Developing a Web service may be simple, but debugging it can be tricky, so this chapter includes some techniques for diagnosing errors. Finally, because there is more than one way to build a Web service, you can find some good, commonsense guidelines in this chapter’s “Best Practices” section.

Understanding Design Considerations

Web service design elements essentially revolve around the following questions:

  • What Web service interface, in terms of its granularity, vocabulary, and payload, would be most appropriate? Also, how many distinct Web services would be appropriate?

    Note

    ▸ For packaging and implementation guidance, seeUnderstanding Web Service Package Considerations,” p. 1108 and “Best Practices,” p. 1160.

  • Should the service mode of interaction be synchronous or asynchronous?

    Note

    ▸ For information on service interaction modes, see “Web Service Types,” p. 1068.

  • For service operations that return multiple values, will the interface benefit from use of output or inout parameters, that is, Holder classes? Remember that any aggregate result information can always be embodied in a user-defined return type; however, output parameters can provide convenience and elegance to an interface. This is particularly relevant if your client programming language naturally supports output or reference arguments.

  • Should you use user-defined type classes in parameters and return values, or use built-in types exclusively? Although user-defined types require a little more work, they can add richer semantics and context to an interface and “behavior” to parameters or return values.

  • What types of J2EE components are most appropriate for your Web service? Table 30.1 provides some guidelines on component selection.

Table 30.1. Suitable J2EE Components and Services for a Web Service

Web Service Characteristic

Recommended Component

Asynchronous

MDB with JMS support

Process oriented

Stateless EJB

Data oriented

JMS and MDB

Scalable and reliable

Stateless EJB

Transactional

Stateless EJB

Simple, not time- or resource-intensive business logic; single-threaded

Java class

Note

▸ For more information on the types of components you can use with your Web service, see “Writing Back-End Components,” p. 1126.

Introducing the WebLogic Build Tools

WebLogic provides tools for assembling your components into a Web service. The components include the following prebuilt items:

  • Server-side components—Compiled Java classes and EJBs. In addition, EJBs must have appropriate deployment descriptors and have been successfully run through the EJBC compiler and optionally packaged in a JAR (Java Archive) file.

  • User-defined types (UDTs)—Compiled and in your CLASSPATH. User-defined types are Java classes that you have defined as opposed to object types that are native to Java. If your UDTs are conforming JavaBeans and you are depending on WebLogic to generate the serializers and deserializers for you, make sure that these UDTs are well formed (see the section “Using User-Defined Data Types” later in this chapter).

  • XML Serializers and deserializers—Compiled and in your CLASSPATH.

  • Message handlers—Compiled and in your CLASSPATH.

  • Holder classes—Compiled and in your CLASSPATH.

    Figure 30.1 shows conceptually what the supplied tools and Ant tasks actually do for you.

    How WebLogic Server build tools work.

    Figure 30.1. How WebLogic Server build tools work.

The circles in Figure 30.1 represent tool processes, and the arrows represent inputs to or outputs from these processes. Inputs and outputs are either J2EE or Java files.

ServiceGen does not actually take any custom serializers/deserializers, custom Holder classes, or handlers into consideration when generating your Web service. It can generate serializers/deserializers for you, but you must post-process the Web service EAR file to configure handlers and holders into the Web service. The following sections describe each of these diagrams and components in greater detail.

The Web Service Home Page

One nifty feature of the WebLogic Web service container is that it can generate a home page for each deployed Web service. Not only does it describe the service in a more friendly and human-comprehendable manner (as opposed to reading the WSDL file, which is intended more for a programmatic consumer), but it also allows you to test the service so that you can see both the outgoing SOAP message and the incoming SOAP response. You can also download the client JAR for this service from this page. Figure 30.2 shows the home page for the WebLogic-supplied example of a stock trader Web service.

The WebLogic-generated Web service home page.

Figure 30.2. The WebLogic-generated Web service home page.

You can click the Service Description link to see the associated WSDL file. You get a bulleted list of available operations on this service, and each operation name is a link to its description/invocation page, shown in Figure 30.3. Obviously, there is some work ahead for WebLogic to provide ways to add documentation, most notably semantics, to both the operation and each parameter. The most logical place for housing such documentation would be the Web service deployment descriptor file (discussed later).

The test page for invoking a Web service operation.

Figure 30.3. The test page for invoking a Web service operation.

The first parameter in this example is the stock ticker symbol, and the second parameter is the number of shares to buy. Specifying BEAS and 100 and then clicking the Invoke button moves you to the results page shown in Figure 30.4.

The results of testing a “buy” service operation.

Figure 30.4. The results of testing a “buy” service operation.

You might be wondering what happens if your operation takes user-defined type parameters. There is a simple solution to that situation: Instead of providing input text boxes for what could potentially be a complex UDT structure with many fields in a hierarchy, the test page will display the XML representation of each parameter in a text box, for you to author. Figure 30.5 shows an example for the Tax Web service that comes with the WebLogic Portal’s sample Commerce application.

Sample WebLogic Commerce Tax Web service invocation, with a user defined data type.

Figure 30.5. Sample WebLogic Commerce Tax Web service invocation, with a user defined data type.

Notice the service parameter is not a primitive Java type but a user-defined type called TaxRequest with two member fields: a string and an array. Note that if you secure your Web service with a security constraint, you will be prompted to log in when you try accessing its home page. This home page feature is quite nifty, considering all these pages are generated on the fly by the WebLogic Web service container.

Anatomy of an Assembled Web Service

A WebLogic Web service is conveniently packaged as a J2EE Enterprise Application Archive (EAR) file. Figure 30.6 shows a typical Web service EAR file, shown expanded for illustration purposes. This Web service is an example that comes with WebLogic Server: the TraderService Web Service based on a stateless session bean back-end using a complex (user-defined) data type called TradeResult for a return value. You can find the code for this example in

The contents of a typical Web service EAR file.

Figure 30.6. The contents of a typical Web service EAR file.

<wl_home>weblogic700samplesserversrcexampleswebservicescomplex statelessSession

Because TradeResult is JavaBean conforming and simple, you can let WebLogic generate its serializer/deserializer class for you, a process known as autotyping. In fact, this EAR file was generated by the WebLogic build tool, an Ant task called ServiceGen.

The trader.ear file is deployed like any other J2EE enterprise application in WebLogic Server. When you run the build script for TraderService, it not only builds the EAR but also copies it appropriately to the applications area of the Examples domain. When the Examples server starts, the TraderService EAR is deployed dynamically.

The numbers in the following list correspond to those shown in Figure 30.6:

  1. Trader.jar is the same EJB JAR file that was input to ServiceGen; it contains the back-end EJB TraderBean for this Web service, as well any user-defined classes used in the EJB method signatures. The EJB is included here because it will be deployed as part of the enterprise application.

  2. application.xml is the enterprise application configuration file. It deploys a Web application called trader_service.war and maps it to the context root webservice. It then deploys the back-end EJB trader.jar.

  3. trader_service.war is the actual Web service implemented as a J2EE Web application. It is composed of the client file TraderService_client.jar and the basic components of the Web application, WEB-INF.

  4. TraderService_client.jar contains all the classes, proxies, and so on needed by a client when invoking this Web service. Its contents will be discussed later. It is needed in this Web application because the service home page has a link for downloading this client file. If the Web service EAR file contains more than one Web service (that is, the deployment descriptor web-services.xml contains more than one Web service definition), there will be one client JAR file per service.

  5. WEB-INF contains this Web application’s resources, as described in points 6 through 9.

  6. web.xml is required in any Web application, but in this case it is empty by default. If you need to secure this Web service, you can add a security constraint clause here (discussed later).

  7. web-services.xml is the deployment descriptor for this Web service. It specifies every detail of a Web service, from its name to its back-end component, its serializers and deserializers, handlers, and so on. This file will be discussed in detail later.

  8. The CLASSES folder contains all necessary Java code to run this Web service (including Java source files). As shown in Figure 30.6, it contains any user defined types (TradeResult.class), any generated serializers/deserializers (xxxCodec.class) for user-defined types, and generated Holder classes for output parameter support (xxxHolder.class). Holder classes are generated by default. If this Web service has any back-end Java classes that house business logic, they are also found here. And, of course, any other user-written supporting classes are also included here.

  9. The LIB folder contains any supporting JARs for the service or its components.

Notably missing from this list is the Web service WSDL file. And how about the Web Service home page (.jsp)? These files are generated dynamically at runtime. When the Web service home page is requested via the home page URL, the Web services container will generate it based on the web-services.xml file. The same is true with the WSDL file: It is generated based on web-services.xml when its URL is requested or if the WSDL link of the service home page is clicked.

The URL for a WebLogic Web service is patterned after

http://<host>:<port>/<context-root>/<web-service-name>

where <context-root> is what was defined in the enterprise application’s application.xml file—in this case, webservice (refer to Figure 30.6). Only one Web service, named TraderService, is defined in this EAR’s web-services.xml file. Hence, this Web service will recognize a <web-service-name> of TraderService. So, the URL for this particular service would be

http://<host>:<port>/webservice/TraderService

If there is more than one Web service defined in web-services.xml, each unique service-name will yield a unique service URL (and unique home page and WSDL).

When a GET command is sent to this URL, it generates the appropriate Web service home page and serves it back to the browser. If you add the URL parameter WSDL, as in

http://<host>:<port>/webservice/TraderService?WSDL

WLS will generate and return the appropriate WSDL file for the service.

When a POST command with text/xml content type is sent to this URL, it interprets the command as a SOAP message, processes the request, and returns a SOAP response (unless the service is configured to return void).

The Client JAR File

The client JAR file is an all-inclusive archive that can be supplied to a client; it contains everything that a client needs for invoking a specific Web service. As a Web service provider, you should provide this JAR file to your clients or make it available for download. Table 30.2 describes the items that come with this client file.

Note

▸ For a discussion of static proxies shown below, see “Static,” p. 1082.

client.jar generally contains the files shown in Table 30.2 and can be generated by WebLogic unless specified otherwise.

Table 30.2. Contents of the WebLogic-Generated Client JAR File

Content Item

Files in This Example

Comments

User-defined types (UDTs)

TradeResult.class

These types are written by the user.

UDT serializers/deserializers

TradeResultCodec.*

The * indicates Java and Class files.

Specific service factory interface

TraderService.*

Specific service factory implementation

TraderService_Impl.*

The client instantiates this object to obtain a port.

Specific service port interface

TraderServicePort.*

This object is used for invoking operations.

Specific service port implementation

TraderServicePort_Stub.*

Holders (for inout parameters)

TradeResultHolder.*

Service WSDL file

TraderService.wsdl

This file is for convenience only so that the client does not need to retrieve it from the server. It is not used by WebLogic.

Web service client-side deployment descriptor

TraderService.xml

This file contains UDT-to-ser/deser mapping.

The client also needs the serializers and deserializers because the Java call needs to be translated to an XML SOAP request, and the XML SOAP response from the server needs to be translated back to Java objects.

A serializer/deserializer class is generated for each user-defined type that WebLogic encounters. A Holder class is generated for each UDT regardless of whether a UDT is used as an out or inout parameter.

If you are using the dynamic client style of invocation, all the generic proxies you need are already in the WebLogic webserviceclient.jar file. But dynamic clients still need this client JAR for serializers, holders, client deployment descriptors, and so on. Service specific proxies (for static clients) are always generated so that the Web service can be invoked either statically or dynamically.

Developing Your Own Web Service

Having decided on the issues described in the preceding sections and knowing what back-end methods to expose as Web service operations, you are now ready to use the tools and mechanisms provided by WebLogic Server to assemble your components into Web services. Figure 30.7 guides you through this process.

Follow these steps to create your WebLogic Web service.

Figure 30.7. Follow these steps to create your WebLogic Web service.

The following list corresponds to the numbered points on Figure 30.7:

  1. Develop your back-end components, whether they are EJBs or Java classes.

  2. If your methods use user-defined types for parameters or result values, write or possibly generate those types.

  3. If you already have a WSDL file for this service, you can choose to run the WebLogic ClientGen tool (or autotype or wsdl2service Ant task with WLS Service Pack) and have it generate the UDT classes for you. Otherwise, you need to write them yourself. The serializers and deserializers can, in most cases, be generated for you. The wsdl2service task will actually generate an implementation skeleton.

  4. At this point, you can invoke the WebLogic ServiceGen tool to generate a Web service EAR file from your back-end components. By default, ServiceGen puts all your back-end methods into one Web service. However, you can control what methods are built and into which Web service they are placed if you decide to create multiple Web services. See “Understanding Web Service Packaging Considerations” later in this chapter for more information.

    In the service element of ServiceGen, specify the attribute expandMethods=true so you have the option of customizing your Web service deployment descriptor later, if necessary. Specify the attribute generateTypes=true so the serializers/deserializers will be automatically generated for you.

    Note

    Your Web service is complete now if you are creating synchronous RPC services, and you don’t need to implement handlers or non-conforming user-defined types.

  5. If you are using handlers to intercept SOAP messages, you need to develop your Handler classes. WebLogic cannot generate Handler classes for you. See “Writing Message Handlers” in this chapter for details.

  6. You now need to configure the Web service to invoke the appropriate handler at the designated moment. To do this, expand (or unjar) your Web Service EAR file and edit the web-services.xml file to add Handler invocation attributes. See “Writing Message Handlers” for details.

  7. Are you using any user-defined type classes that WebLogic Server might not be able to automatically configure into your Web service for you? That is, WebLogic Server cannot generate an XML Schema, serializer class, and so on for you because your UDT is non-conforming. If so, you need to write these classes yourself. (Refer to “Using User-Defined Data Types” later in this chapter for the definition of non-conforming.)

  8. You now need to update the generated web-services.xml file to add the XML Schema under <types>, and mapping information between UDT classes and their respective serializer classes under <type-mappings>. See “Using User-Defined Types” for information on how to make these changes.

  9. Another consideration is whether you are implementing any asynchronous service operations. If so, you need to configure your JMS queues and destinations via the WebLogic Server Administration Console. Of course, the listener objects must be defined and written, which might involve message-driven beans (MDBs).

  10. You need to deploy any MDB listeners yourself. See “Writing Asynchronous Web Services” in this chapter for details.

  11. After you complete the preceding steps, you can either jar up the components into an EAR file again or deploy it as an expanded EAR. Be warned that if you run ServiceGen again, any manual changes you have made in web-services.xml will be lost.

WLS 7.0 Service Pack 1 offers more helpful tools for developing Web services. See “New Features in WLS 7.0 Service Packs” in this chapter for details.

WebLogic Ant Tasks

As shown in Figure 30.1, WebLogic provides two main Ant tasks for assembling Web services: ServiceGen and ClientGen. They are meant to be invoked from within an Ant build file, as part of building the components that constitute your Web service.

ServiceGen

A Web service provider uses the ServiceGen task to automatically assemble various components and resources into a WebLogic Web service. Figure 30.8 shows a schematic of the ServiceGen task.

The make-up of the ServiceGen task.

Figure 30.8. The make-up of the ServiceGen task.

As you can see, it is possible to define more than one Web service in a single deployable Web service EAR. This topic is discussed in more detail later, in “Understanding Web Service Packaging Considerations.” Each service box represents the creation of a new Web service. A client subelement in a service directs the creation of a client JAR file for that service. Listing 30.1 shows the ServiceGen invocation for the TraderService example discussed previously.

Example 30.1. Ant Build Code Snippet for Invoking ServiceGen

1  <taskdef name="servicegen"
      classname="weblogic.ant.taskdefs.webservices.servicegen.ServiceGenTask"/>
  <target name="build-ear">
    <servicegen
2       destEar="trader.ear"
3       warName="trader_service.war"
4       contextURI="webservice">
5       <service
            ejbJar="traderBean.jar"
            targetNamespace="http://www.bea.com/examples/Trader"
            serviceName="TraderService"
            serviceURI="/TraderService"
            generateTypes="True"
            expandMethods="True" >
6            <client
                packageName="examples.webservices.complex.statelessSession"
                clientJarName="traderService_client.jar"
            />
        </service>
    </servicegen>
  </target>

First, you define a task called servicegen mapped to the WebLogic servicegen class (1). Actually, the first line of Listing 30.1 is unnecessary if you are using the Ant that is bundled with WLS. You then define an Ant target called build-ear requesting that it create an EAR file called trader.ear (2) and a WAR file called trader_service.war. If you do not specify a file extension here, servicegen will interpret these names as file folders where an expanded EAR and expanded WAR will be created. The next attribute, contextURI, defines the first part of the Web service URL, called the context root of this enterprise application. The URL of this service is defined as

http://<host>:<port>/<contextURI>/<serviceURI>

Line 5 starts a Web service definition, using traderBean.jar for back-end EJBs and TraderService as the Web service name in its WSDL file. The attribute serviceURI defines the last part of the preceding URL for this Web service. In WebLogic, each Web service is deployed as a Web application, so serviceURI is actually the Web application’s web-uri. Because the back-end EJB TraderBean uses the user-defined type TradeResult, you can specify generateTypes="True" so that ServiceGen will autotype and generate the serializer and deserializer class for it.

Line 6 requests that ServiceGen also generate the client JAR file and store it in the Web service EAR, which must be done if you want the client JAR to be downloadable from the Web service home page (and also not rename the client JAR, but assume its default name). See the next section, “ClientGen,” for details on clientgen parameters. The only difference between this <client> element of <service> and the ClientGen task is that with <client> you cannot specify a folder location for the destination client JAR; it must be a filename. Another difference is that <client> uses the deployment descriptor and back-end component interface when generating the client JAR, whereas ClientGen can take a WSDL file to generate the client JAR.

ClientGen

As Figure 30.1 shows, ClientGen can take a service description in the form of a WSDL file or an actual Web service EAR file and generate a client JAR file that contains everything a client needs to invoke the Web service. Listing 30.2 shows a simple ClientGen invocation.

Example 30.2. Sample ClientGen Invocation

<taskdef name="clientgen"
     classname="weblogic.ant.taskdefs.webservices.clientgen.ClientGenTask"/>
<target name="gen_clientjar" >
  <clientgen
     wsdl="http://localhost:7001/webservice/TraderService?WSDL"
     packageName="examples.wlbook.web.services"
     typePackageName="examples.wlbook.udt"
     autotype="True"
     clientJar=".	est-client.jar"
  />
</target>

This scriptlet obtains the WSDL of the TraderService example we’ve been discussing and then parses it to detect uses of any non–built-in types so it can generate Java source files for them. It also generates serializer and deserializer classes for each non–built-in type it finds (because you specified autotype="True"). Of course, the client proxies for this service are also generated into the client JAR. The output JAR file is also named.

Tip

When ClientGen generates the client proxies, what package should they reside in? ClientGen has no idea, so you must tell it using the packageName attribute. Similarly for generated non–built-in types and serializers, you specify their package name using the typePackageName attribute.

Letting ClientGen Generate UDTs for You

In one use case, WebLogic can generate user-defined types (also known as non–built in types) for you: when you invoke ClientGen with an input WSDL description and the service operations use complex types. In this case, ClientGen will generate the actual Java source files that correspond to each complex type. It will even generate serializers and deserializers for those objects. The only effort on your part is to obtain or author the WSDL file.

Command-Line Utilities

WebLogic also provides command-line versions of ServiceGen and ClientGen, for use in non-Ant build environments. In this case, the invocation attributes are specified as command-line options, named to match (more or less) with the Ant task interface.

Understanding Web Service Packaging Considerations

If you have multiple EJBs and/or Java classes sporting your business logic, how many distinct Web services should that translate to? Figure 30.9 conceptualizes this question.

Decide how many distinct Web services should be created.

Figure 30.9. Decide how many distinct Web services should be created.

WebLogic gives you maximum flexibility in deciding how best to package your myriad business logic back-end components into Web service offerings. Some questions in this regard are

  • Should you combine all your component methods (EJB or Java class methods) into one single Web service that possesses all those operations?

  • Should you categorize your business methods in some way—for example, by functionality or domain—and create a distinct Web service per category?

  • Is a distinct Web service one that has its own unique service name or context root, or both?

The following is one very important guideline that might answer all these questions:

Try not to expose your component or application model as your Web service model.

First, your component model is probably too fine grained. Web services should be coarse grained to reduce the number of network calls and increase service reliability. Second, a Web service should interact at a business level. A component API typically reflects your technical framework or architecture, which by nature uses different, nonbusiness objects and vocabulary. The exception to this rule is if the Web service functions as internal plumbing; that is, it is an intra-enterprise Web service. We will discuss best practices later in this chapter. For now, see what options you have with ServiceGen. Figure 30.10 diagrams these options.

How ServiceGen can organize your Web services.

Figure 30.10. How ServiceGen can organize your Web services.

The following discussion relates to Figure 30.10:

  • Methods in all selected EJBs and specified back-end class files within a <service> element will be combined into one Web service. The implication here is that the method names cannot collide in the same Web service; that is, they must be unique. Otherwise, ServiceGen will warn you that method overloading is not supported.

  • You can pick and choose what particular back-end EJBs in an input EJB JAR file to deploy as a Web service. This is done using the includeEJBsor excludeEJBs (but not both) attribute of the <service> element. Both attributes take comma separated EJB names as arguments. The attribute includeEJBs lists the EJBs in the input JAR file that should be made into Web services, whereas excludeEJBs says to include all EJBs found in the input JAR file except those listed. If neither is specified, all EJBs found will be merged into one Web service.

    Figure 30.10 shows an EJB JAR containing three EJBs: ejb1, ejb2, and ejb3. Service1 includes only ejb1 and ejb2. Hence, the Web service represented by //host:port/context1/Service1 will have operations from both ejb1 and ejb2.

    Service2, on the other hand, includes only ejb3 and a back-end Java Class1, by exclusion. Hence, the Web service represented by //host:port/context2/ Service2 will have operations from both ejb3 and Class1.

    Note

    Nothing prevents you from including the same back-end component in multiple Web services, although there is no good reason to do so.

  • To create more than one Web service (multiple unique serviceNameURIs) that shares the same context root, specify multiple <service> elements in ServiceGen. Each <service> element translates to a separate Web service within one context root. Of course, the service names within that context must be unique.

  • To create multiple Web services with different context roots, you must invoke ServiceGen separately for each context root. In that case, service names do not need to be unique across context roots.

Before we leave this topic, let’s consider some grouping guidelines. First and foremost, each Web service should possess methods that are related in some way. Second, your back-end components may have interdependencies that require them to be packaged in the same Web service. Another consideration that applies only if your back-end components are secured is whether one set of credentials will permit usage of every EJB included in that service.

Building WebLogic Web Services Manually

The preceding section showed how WebLogic Web services can be created relatively easily with the tools and tricks provided. After you create your back-end components, exposing them as Web services is an almost trivial task because most of the needed files are generated for you. One of these files is the Web-services deployment descriptor, a new construct that someday should become part of J2EE (refer to JSR 109). In some instances, creating this file manually or editing a generated file is necessary, as in these examples:

  • If you are using a JMS destination for a back-end component. (However, in WLS SP1 the ServiceGen Ant task can now also generate a JMS-based Web service.)

  • If you are using handlers or non-conforming user-defined types.

The Web Service Deployment Descriptor

In the spirit of J2EE deployment descriptors, WebLogic Server has formulated a declarative style of defining Web services: web-services.xml. Although allowing ServiceGen to generate this file for you is preferred, in some instances ServiceGen may not be adequate. You then need to code web-services.xml yourself. To aid you in this task, this section reveals the format and function of this file.

Figure 30.11 diagrams the major sections of the web-services.xml file. A box represents an enclosed XML element, usually with a begin and end tag. Embedded boxes represent embedded XML elements. An ellipsis (...) following a box name indicates an optionally repeating element.

The sections of a web-services.xml file.

Figure 30.11. The sections of a web-services.xml file.

The two major sections of a Web service deployment descriptor are handler chains and one or more Web service definitions. As you may recall from the preceding chapter, a handler accesses a SOAP request, possibly altering or enhancing it in some way, before passing it on to a back-end component that fulfills the service request. That same handler can also access the response before returning it to the client. In some cases, a handler can even fulfill a request without invoking a back-end component. A Web service section describes everything about a service, from its back-end components to data types to operations.

The handler chains box is optional and can be used to define one or more handler chains. A handler chain specifies a sequence of Handler classes to be invoked before a SOAP message reaches its destination. At this point, you are only defining them but not specifying which Web service will use them or where in the service process they might be invoked. A handler chain will be referenced in the operation element in a Web service, defined later. As you can see, Handler classes can be shared across different Web services.

The Web service box defines a single Web service. You can actually define more than one Web service in this file. If you do, all such Web services will be part of the same Web application, which means they will all have the same context root. For example, if you had a second Web service named TickerService defined in the web-services.xml file of the previous TraderService example, that second service’s URL would be

http://<host>:<port>/webservice/TickerService

The Web service box embeds four other subelements:

  • Back-end components—Contain business logic for fulfilling a service operation request. They reference deployed EJBs, classes, or objects in the JNDI tree. This section is optional.

  • User-defined data types—Used in the Web service calls, defined in XML Schemas. These data types are non–built-in types and therefore must be specified by the user for publishing in the WSDL file. You can specify as many data types as necessary. This section is optional.

  • UDT mappings—Specify what Java class and serializer/deserializer class each UDT corresponds to. This section is required if UDTs are defined.

  • Operations section—Lists what operations are available in this service and what business logic back-end component will be invoked to fulfill the request. This section is required.

Figure 30.11 also shows schematically that, during operation definition, the UDTs, components, and handler chains are referenced and used in context.

A Sample web-services.xml File

This section takes apart a simple Web service to see what its web-services.xml file looks like. This sample Web service does the following:

  • Uses a Java class back-end component (examples.ws.getTicker) for an operation named getTicker that returns an official ticker symbol given a company name string

  • Uses a stateless session EJB back-end component with an operation named buy that executes a stock buy, returning true if the trade was successful

  • Is a synchronous RPC service

  • Has no user-defined types nor handlers

Its web-services.xml file is shown in Listing 30.3.

Example 30.3. The Sample web-services.xmlFile

<web-services>

1   <web-service name="Trader"
                 targetNamespace="www.bea.com/Trader"
                 uri="/TraderWS">

2      <components>
          <java-class name="getTickerClass"
                      class-name="examples.ws.getTicker" />
          <stateless-ejb name="Trader">
             <ejb-link path="trader.jar#TraderBean" >
          </stateless-ejb>
       </components>

3      <operations>

         <operation name="getTicker"
                     component="getTickerClass"
                     method="getTicker"
                     invocation-style="request-response" >
             <params>
                <param name="companyName" location="body"
                       style="in" type="xsd:string"
                       class-name="java.lang.String" />
                <return-param name="tickerSymbol"
                        location="body" type="xsd:string"
                        class-name="java.lang.String" />
             </params>
          </operation>

4          <operation name="getShares"
                     component="Trader"
                     method="buy" >
            <params>
5               <param name="ticker" location="body"
                       style="in" type="xsd:string"
                       class-name="java.lang.String" />
                <param name="numShares" location="body"
                       style="in" type="xsd:int"
                       class-name="int" />
6               <return-param name="result" location="body"
                       type="xsd:boolean"
                       class-name="Boolean" />
             </params>
          </operation>

       </operations>

    </web-service>

</web-service>

The following numbered list corresponds to the numbers shown in Listing 30.3:

  1. Start a new Web service. The required attributes shown are

    • name—. The name of the Web service that will be used in its WSDL file

    • targetNamespace—. To be used for qualifying namespaces in the WSDL file as well as any generated SOAP message

    • uri—. To be used as part of the Web service URL

    You can define more than one Web service in a web-services.xml file.

  2. List your back-end components here. At this point, you don’t know how, where, or if these components will be mapped to operations. It is just a list.

    The first component is a Java class. The class needs to be accessible to the Web service container at runtime. To make it accessible, you usually locate such a class in the WEB-INFclasses folder of the service Web application (or service WAR file).

    The second component is a stateless session EJB. Notice the <ejb-link> path attribute, which must point to the JAR file where the EJB is located, and the bean name as specified in the <ejb-name> element of the bean deployment descriptor. This JAR must have been successfully run through the EJB compiler.

    If your Web service uses only handlers exclusively for fulfilling service calls, you can omit the entire Components section.

    Now comes the task of defining the operations or calls available in this Web service.

  3. The first operation will be named getTicker in the generated WSDL file. It is backed by the component getTickerClass, which you defined in the Components section. Specifically, when this service operation is called, the container needs to invoke the getTicker method of getTickerClass.

    What if you want every method in this class to be exposed as a service operation, where the operation names can equal the method names? Is there a shortcut for specifying that? Yes, you can just use the following:

    <operation component="getTickerClass"
        method="*">
    </operation>
    

    Obviously, you need to omit the <params> element in this case. What if your back-end component has overloaded methods, such that only referring to a method name would be ambiguous? In that case, you can specify a method signature as follows:

    <operation name="getTicker"
               component="getTickerClass"
               method="getTicker(java.lang.String)"
    </operation>
    

    The last attribute used here is invocation-style, which specifies whether this is synchronous (with a value of "request-response") or asynchronous (with a value of "one-way").

  4. Now look at the optional <params> subelement in the next operation, getShares, which is backed by a stateless session bean. If <params> is omitted, the container will examine the back-end component to determine each parameter’s type and supply default parameter names. In that case, why specify <params> at all? Do so only if you need to do the following:

    • Give parameter names in the WSDL file that are more meaningful

    • Use output or inout parameters

    • Map a parameter to a name in the SOAP header of the request or response

  5. The location attribute specifies where this parameter’s value can be found: the SOAP header, SOAP body, or SOAP attachment. That is, you can ask the container to extract a SOAP header element for you and pass it in to the back end component method. Typically, as it is in this case, the parameters are passed via the client’s service call parameters. The style attribute (different from invocation-style of <operation>) declares the mode of the parameter: in, out, or inout.

  6. The next item to point out is the class-name attribute. Why do you need to specify the Java class name of the type attribute? Because type is required and points to an XML data type with a known mapping to a Java type, why specify another Java type in class-name? First, class-name is optional. Second, you specify class-name only to clearly identify an XML type because XML types do not distinguish between Java primitives and their corresponding wrapper classes (for example, boolean and java.lang.Boolean are both XML-typed as xsd:boolean). In Listing 30.3, the return type is explicitly defined to be a primitive boolean. Also, if your method takes a base class A and the client is supposed to pass a subclass B (class B extends class A), you can specify subclass B in web-services.xml, and the WSDL will be generated based on B, which more accurately reflects what type the user should pass.

You have just seen the basic elements of web-services.xml. This example does not cover user-defined types, output parameters, handlers, and asynchronous Web services. They are covered in the ensuing sections. Figure 30.12 shows what the web-services.xml file would look like with all possible elements.

The web-services.xml file with all possible elements.

Figure 30.12. The web-services.xml file with all possible elements.

Using User-Defined Data Types

Sometimes your Web service might require the use of classes, in parameters or return values, that are user defined—in other words, not built into the Java language or WebLogic Server. If your back-end component(s) that uses these user-defined types is already written, chances are your UDT Java classes are also already written. In most cases, the WebLogic ServiceGen task should be able to examine these UDTs and generate the following for you:

  • XML Schema for the UDT, in the <types> section of the Web service deployment descriptor file, web-services.xml

  • Serializer and deserializer classes for the UDT

  • Type-mapping elements in web-services.xml, which map UDTs to their serializer/deserializer classes

These are all necessary items in the make-up of a WebLogic Web service. The good news is that if your UDT conforms to the following rules, WebLogic Server should be able to successfully generate them for you:

  • Define a default, public, zero-argument constructor.

  • For each instance variable, define public setter and getter methods, set Xxx() and getXxx(), respectively, where Xxx is the name of the instance variable. This is a JavaBean characteristic.

  • If any of your UDT instance variables are themselves UDTs, make sure you also have these UDTs defined.

Otherwise, you need to manually create these items. The following sections describe how this can be done.

Specifying XML Schema for UDTs

You need to decide how your user-defined type will look in the XML representation. For instance, what element names and tag structure will be used? Which elements are required, and which ones are optional? How many times can a particular element occur? The XML representation you define will be used in the XML SOAP request and possibly in the SOAP response.

You define how your UDT will “look” in XML using the precise language of XML Schemas. Learning how to author XML Schemas is not a trivial task and is certainly beyond the scope of this book. We can, however, show a sample schema definition for a typical UDT, Employee, as defined in Listing 30.4. (Note that the Employee class conforms to the rules presented in the preceding section.)

Example 30.4. Sample User-Defined Type Employee

public class Employee {
   private String fullname;
   private int id;
   public Employee() {}  // Create a default employee
   // set/get methods for each variable for JavaBean compliance
   public setFullname(String name) {
      fullname = name;
      }
   public getFullname() {
      return fullname;
   public setId(int serialNo) {
      id = serialNo;
      }
   public getId() {
      return id;
      }
}

A possible XML representation for an instance of Employee might be

<employee>
  <fullname>Christy Anne Go</fullname>
  <id>98345</id>
</employee>

Now you must codify the rules indicating how to convert an instance—in fact, any instance—of Employee into the preceding XML format. What you need is a meta language to describe a grammar for what are valid XML sequences that represent a correct Employee instance. That is exactly what an XML Schema is. A possible schema for Employee is shown in Listing 30.5.

Example 30.5. A Sample XML Schema for a User-Defined Type

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
           xmlns:stns="java:examples.webservices"
           attributeFormDefault="qualified"
           elementFormDefault="qualified"
           targetNamespace="java:examples.webservices">
   <xsd:complexType name="Employee">
       <xsd:sequence>
           <xsd:element name="fullname"
                        type="xsd:string"
                        maxOccurs="1" minOccurs="1">
           </xsd:element>
           <xsd:element name="id"
                        type="xsd:int"
                        maxOccurs="1" minOccurs="1">
           </xsd:element>
       </xsd:sequence>
   </xsd:complexType>
</xsd:schema>

This schema declares the following:

  • Element complexType defines a user-defined type called Employee.

  • Element sequence lists the members of Employee and indicates that each member must occur in the order shown.

  • Element element defines a member, called fullname, of type string. The fullname member must occur exactly once, to be a valid Employee instance. The same applies to the other members.

Note

Go to http://www.w3.org/TR/xmlschema-0/ for more information on the XML Schema notation definition.

Defining Types in web-services.xml

After you write the XML Schema for your UDT, you must add it to the <types> section of web-services.xml, as shown in the following code:

<types>
  <!-- One user-defined type -->
  <xsd:schema... >
    :
  </xsd:schema>

  <!--Another user-defined type -->
  <xsd:schema... >
    :
  </xsd:schema>
</types>

You can define multiple UDTs within a schema section, but all UDTs in a schema section will share the same target namespace.

Exactly when is an XML Schema actually used by the container because, in a sense, a UDT’s type schema is already codified in its serializer class? The answer is: The schema is not used when a service call is made and fulfilled. It is used only in generating a service’s WSDL file. However, a UDT schema is still required regardless of whether you do or do not need the WSDL file.

Writing Serializers and Deserializers

A serializer converts a Java object to XML representation, and a deserializer converts an XML document to a Java object. The characteristics of WebLogic/JAX-RPC serializers are as follows:

  • You write one class that contains a serialize and a deserialize method (with prescribed signatures, of course).

  • The class must extend weblogic.webservice.encoding.AbstractCodec.

  • The naming convention (optional) for a serializer class is udt-nameCodec.java, where udt-name is the user-defined class name.

  • Because the WebLogic Web service runtime knows each UDT’s serializer class (from type mappings in web-services.xml), the serialize and deserialize methods are automatically called at the appropriate times. The only exception to this rule occurs when a serializer class must also serialize an embedded UDT. In this case, it is the enclosing UDT that invokes its member UDT’s serializer class, not the WebLogic container.

Now look at a sample serializer class for the Employee user-defined type, as shown in Listing 30.6.

Example 30.6. Sample Serializer Class for the Employee User-Defined Type

package examples.newTypes;

import weblogic.webservice.encoding.AbstractCodec;
import weblogic.xml.schema.binding.DeserializationContext;
import weblogic.xml.schema.binding.DeserializationException;
import weblogic.xml.schema.binding.Deserializer;
import weblogic.xml.schema.binding.SerializationContext;
import weblogic.xml.schema.binding.SerializationException;
import weblogic.xml.schema.binding.Serializer;

import weblogic.xml.stream.Attribute;
import weblogic.xml.stream.CharacterData;
import weblogic.xml.stream.ElementFactory;
import weblogic.xml.stream.EndElement;
import weblogic.xml.stream.StartElement;
import weblogic.xml.stream.XMLEvent;
import weblogic.xml.stream.XMLInputStream;
import weblogic.xml.stream.XMLName;
import weblogic.xml.stream.XMLOutputStream;
import weblogic.xml.stream.XMLStreamException;

public final class EmployeeCodec extends
      weblogic.webservice.encoding.AbstractCodec
{
  public void serialize(Object obj,
                        XMLName name,
                        XMLOutputStream writer,
                        SerializationContext context)
    throws SerializationException
  {
    Employee emp = (Employee) obj;
    try {
      //outer start element <Employee>
      writer.add(ElementFactory.createStartElement(name));
      //employee name element
      writer.add(ElementFactory.createStartElement("fullname"));
      writer.add(ElementFactory.createCharacterData(emp.getName()));
      writer.add(ElementFactory.createEndElement("fullname"));
      //employee id element
      writer.add(ElementFactory.createStartElement("id"));
      String id_string = Integer.toString(emp.getId());
      writer.add(ElementFactory.createCharacterData(id_string));
      writer.add(ElementFactory.createEndElement("id"));
      //outer end element </Employee>
      writer.add(ElementFactory.createEndElement(name));
    } catch(XMLStreamException xse) {
      throw new SerializationException("stream error", xse);
    }
  }

  public Object deserialize(XMLName name,
                            XMLInputStream reader,
                            DeserializationContext context)
    throws DeserializationException
  {
    // extract the desired information out of reader, consuming the
    // entire element representing the type,
    // construct your object, and return it.
    Employee employee = new Employee();
    try {
      if (reader.skip(name, XMLEvent.START_ELEMENT)) {
        StartElement top = (StartElement)reader.next();
        //next start element should be the employee name
        if (reader.skip(XMLEvent.START_ELEMENT)) {
          StartElement emp_name = (StartElement)reader.next();
          //assume that the next element is our name character data
          CharacterData cdata = (CharacterData) reader.next();
          employee.setName(cdata.getContent());
        } else {
          throw new DeserializationException("employee name not found");
        }
        //next start element should be the employee id
        if (reader.skip(XMLEvent.START_ELEMENT)) {
          StartElement emp_id = (StartElement)reader.next();
          //assume that the next element is our id character data
          CharacterData cdata = (CharacterData) reader.next();
          employee.setId(Integer.parseInt(cdata.getContent()));
        } else {
          throw new DeserializationException("employee id not found");
        }
        //we must consume our entire element to leave the stream in a
        //good state for any other deserializer
        if (reader.skip(name, XMLEvent.END_ELEMENT)) {
          XMLEvent end = reader.next();
        } else {
          throw new DeserializationException("expected end element not found");
        }
      } else {
        throw new DeserializationException("expected start element not found");
      }
    } catch (XMLStreamException xse) {
      throw new DeserializationException("stream error", xse);
    }
    return employee;
}

The following is the serialize method signature:

public void serialize(Object obj,
                      XMLName name,
                      XMLOutputStream writer,
                      SerializationContext context)
  throws SerializationException

The following are the parameters:

  • Object is the Java object to be converted to XML.

  • XMLName is the top-level element name of the resulting XML.

  • XMLOutputStream is a writer in the WebLogic XML streaming API, and is the place where the converted XML elements will be written.

  • SerializationContext is used for maintaining context across embedded serialize calls. For example, if your UDT contains a member UDT2, you will need to call UDT2’s serialize method and pass this same SerializationContext onward. Do not in any way change this object.

Now look at the deserializer method signature:

public Object deserialize(XMLName name,
                          XMLInputStream reader,
                          DeserializationContext context)
  throws DeserializationException

The following are the parameters:

  • XMLName is the top-level element name of the XML code to be converted.

  • XMLInputStream is a reader in the WebLogic XML streaming API. The XML you want to convert is contained here.

  • DeserializationContext is used for maintaining context across embedded deserialize calls. For example, if a complexType contains a member complexType2, you need to call complexType2’s deserialize method and pass this same DeserializationContext onward. Do not in any way change this object.

When you’re converting an embedded user-defined type, there is no way to look up its serializer/deserializer class. You just need to hard-code any embedded serializer class invocations.

Defining Type Mappings in web-services.xml

The last step in this process is to state which serializer/deserializer class should be called for each user-defined type. If WebLogic Server is generating your serializer class, its type mapping will also be generated for you. Otherwise, you need to add a type-mapping section to web-services.xml, as illustrated in Figure 30.12. The <type-mapping> element starts this section and encompasses one or more <type-mapping-entry> subelements, as shown in Listing 30.7.

Example 30.7. Type-Mapping Entry for User-Defined Type Employee

<type-mapping>
  <type-mapping-entry
       class-name="examples.ws.Employee"
       xmlns:p1="java:examples.webservices"
       type="p1:Employee"
       serializer="examples.ws.EmployeeCodec"
       deserializer="examples.ws.EmployeeCodec" >
  </type-mapping-entry>
</type-mapping>

The following are the parameters:

  • class-name is the fully-qualified Java class of the user-defined type.

  • type points to the XML Schema that describes the user-defined type. Note that the Employee type needs to be namespace qualified; hence, a namespace definition in the preceding line is necessary (unless it is in the default namespace of the web-services.xml file). This namespace definition value must match the one used in the XML Schema definition, shown in Listing 30.5.

  • serializer is the fully qualified Java class which contains a serialize method that converts data from Java to XML.

  • deserializer is the fully qualified Java class which contains a deserializer method that converts data from XML to Java objects.

You can specify multiple type-mapping entries, one for each of your user-defined classes. WebLogic Server presumes that your serializer class produces the correct Java object or XML document. It cannot verify correctness of your custom-created serializer class at build or deploy time.

Also, ServiceGen will not copy your custom serializer/deserializer classes into the ear file, and if you specify an input type mapping file, it will assume that there is no need to generate serializer/deserializer classes for the types that are already mapped.

Writing Back-End Components

Back-end components are Java objects that implement the business logic behind Web service operations. Your two main choices are stateless session EJBs or Java classes. For guidance on which component might be more appropriate for your Web service, see the section “Understanding Design Considerations” earlier in this chapter. WebLogic’s ServiceGen task can take written back-end components and generate Web services from them.

Stateless Session EJBs

Enterprise JavaBean classes used as back-end components are no different from any other EJB. The only special considerations are the following:

  • If you are using output parameters in your EJB methods, you must use the appropriate holder objects for those parameters.

  • If you are implementing a one-way service, maybe as part of an asynchronous service model, the EJB method must return void. See “Writing Asynchronous Web Services” later in this chapter for more information.

  • The ServiceGen task exposes all public methods (except the native bean methods such as ejbCreate, ejbPassivate, and so on) in your back-end EJB. If this is not your intent, either specify the nonexposed methods to be private in your EJB, or hand-tune the generated web-services.xml file and remove the undesired <operation> elements.

Java Classes

For lightweight business logic, you can use regular Java classes as Web service back end components. However, note the following limitations when doing so:

  • The Java class must define a zero-argument default constructor.

  • The Java class cannot start any threads, directly or indirectly. You must write thread-safe code because only one instance of a back-end Java class will be used (like a singleton) for all its service requests.

  • All public methods in the class will be exposed as Web service operations.

Using Output Parameters

As discussed in the preceding chapter, a Web service can conveniently return multiple values using output parameters in a service call, using the concept of holders. Holders originated in the Common Object Request Broker Architecture (CORBA), where they were used for out and inout IDL parameters. A holder object acts like a box holding a Java object that needs to change in value on a service return. There is a distinct type of holder “box” for each Java type. For instance, the holder box for a String is javax.xml.rpc.holders.StringHolder (see Table 29.1 in Chapter 29 for a list of Holder classes supported by WebLogic Server 7.0). The holder “box” is passed into a service call; the service then opens the box, replaces its content with another Java object of the same type (if immutable) or a different-valued object, and returns the box to the caller.

To see a sample called ws-inout that illustrates a client and service implementation using output parameters, look at Sams Web site, www.samspublishing.com.

Note

▸ For details on the StockTrader service, seeInOut Parameters,” p. 1089. This section shows how its back-end provider (a stateless session bean) is implemented, how to build and run the sample, and how to develop Holder classes for your user-defined type classes.

The Holder Interface

All Holder classes implement the javax.xml.rpc.holders.Holder interface. This interface dictates the following requirements of an implementation:

  • A no-argument default constructor must be defined, initializing the contents of the holder.

  • There must be a constructor that takes one argument of the same data type that this holder embodies.

  • There must be a public instance variable named value that stores the content (data) of this holder, obviously with the same data type as the content data.

A holder object can simply be instantiated and given its initial content using the default constructor, as follows:

intHolder numShares = new intHolder(250);

Alternatively, you can change its value by using its value variable:

numShares.value = 100;

Note

▸ To see how a Web service client passes in holder parameters, seeInOut Parameters,” p. 1089.

InOut Back-End Component

Writing a back-end component that takes output parameters is simple. The service methods just need to take holder-type arguments for any output parameter. Given that, the StockTrader.buy method has the following signature:

Buy (in String ticker, inout int numOfShares, out float sharePrice)

The back-end stateless session bean method that corresponds to this service call is shown in Listing 30.8.

Example 30.8. Back-End Implementation of the StockTrader.buy Service

public void buy (java.lang.String ticker, IntHolder inOutNumShares,
    StringHolder outBuyPrice )
{
// execute the trade using ticker and numShares (code not shown)
. . .

// Use the input parm and update it (inout)
inOutNumShares.value = inOutNumShares.value - 1;

// Set the buy price (out)
outBuyPrice.value = "$22.50";
}

The public variable value is used to both access and update the holder. Note that the same Holder class is used for both out and inout parameters. It is the business logic that decides how to use the holder object. This example shows how to use a built-in type as an output parameter, but using a Holder class for a user-defined type is just as simple. The back-end component accesses the UDT object through the public value variable and modifies the UDT accordingly.

InOut in WSDL and web-services.xml

You might be surprised to learn that Holder classes are never referenced inside a WSDL or the web-services.xml file. Each of these “service description” files has other, more subtle ways of indicating out or inout parameters. This makes perfect sense considering that a service description should not be programming language specific. In the JAX-RPC prescription for Java Web services implementation, Holder classes are used to implement output parameters. So how does a WSDL file express output parameters?

Recall that the <message> element of the tModel section in a WSDL file describes the data items (or data parameters) that are sent between the caller and service provider. Each <part> subelement of a <message> represents one call parameter. There is one <message> element for the input request and another <message> element for the output return. Normally, the return <message> would indicate a return value, but in this case, you can list the same <part> subelements in both the input and return <elements>. The WSDL specification dictates that such a scenario indicates out or inout parameters. Let’s quote the WSDL 1.1 specification to be precise:

  • If a part name appears in only the output message, it is an out parameter.

  • If a part name appears in both the input and output message, with the same type, it is an inout parameter.

These concepts will be clearer when you see the WSDL. Here again is the signature of the buy method:

Buy (in String ticker, inout int numOfShares, out float sharePrice)

Its corresponding input (message name="buy") and output (message name="buyResponse") message elements in WSDL look like Listing 30.9.

Example 30.9. Specifying out and inout Parameters in WSDL

   <message name="buy" >
<part name="string" xmlns:partns=http://www.w3.org/2001/XMLSchema
     type="partns:string" />
<part name="numShares" xmlns:partns=http://www.w3.org/2001/XMLSchema
     type="partns:int" />
   </message>

   <message name="buyResponse" >
<part name="numShares" xmlns:partns=http://www.w3.org/2001/XMLSchema
     type="partns:int" />
<part name="sharePrice" xmlns:partns=http://www.w3.org/2001/XMLSchema
     type="partns:float" />
   </message>

Part name numShares occurs in both the input and output messages, making it an inout parameter. On the other hand, part name sharePrice occurs only in the output message, making it an out parameter.

Note

If you open the generated WSDL for StockTrader, you will notice that sharePrice actually occurs in both input and output messages. That’s because WebLogic Server is generating the WSDL based only on the back-end StockTraderBean.java, where it cannot distinguish between out and inout parameters because a Holder class can be used either as an out or inout parameter.

In web-services.xml, you simply specify the style attribute in the <parm> element, as shown in Listing 30.10.

Example 30.10. Specifying out and inout Parameters in web-services.xml

<operation method="buy"... >
 <params>
   <param location="body" class-name="java.lang.String" style="in"
     name="string" xmlns:xsd=http://www.w3.org/2001/XMLSchema type="xsd:string">
   </param>
   <param location="body" class-name="javax.xml.rpc.holders.IntHolder"
     style="inout" name="intHolder" xmlns:xsd=http://www.w3.org/2001/XMLSchema
     type="xsd:int">
   </param>
   <param location="body" class-name="javax.xml.rpc.holders.StringHolder"
     style="out" name="stringHolder" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     type="xsd:float">
   </param>
 </params>
</operation>

Don’t forget that the WebLogic ServiceGen task will generate all these files for you if you give it a back-end component (a Java class or EJB) that uses Holder classes. However, the preceding discussion should prove helpful if, for some reason, you need to manually add output parameter support for an already-established Web service.

You might think that a UDT Holder class like AddressHolder is itself a UDT that needs an XML Schema and type mapping. That is not the case. Because a Holder class is never referenced inside a service description file, it is an “under-the-covers” UDT that is automatically configured, per JAX-RPC specifications, into the Web service for output parameters.

Now that you’ve seen the client, back-end component, and deployment descriptor of a Web service that uses output parameters, it should be evident that these Holder classes are really nothing special. Holder classes also need serializing/deserializing, except that you don’t need to define a distinct serializer/deserializer for each type of holder object. There is a built-in serializer/deserializer for the javax.xml.rpc.holders.Holder interface. While serializing/deserializing a particular holder object, it simply takes its holder.value object and calls its serializer/ deserializer. That is, your UDT classes always need serialzers/deserializers regardless of their use as in, out, or inout parameters. The only magic that WebLogic Server must perform is intelligently heed the service descriptions’ cues on output arguments and presume use of the appropriate Holder classes in both the client and back-end components.

Running the InOut Sample

As mentioned previously, our Web site contains an example called ws-inout that illustrates output parameters. You can find it in chap30ws-inout. This example assumes you already have the WebLogic Server component of WebLogic Platform 7.0 installed or accessible, including the WebLogic examples. You need the Examples server that comes with installing the WebLogic Server Examples bundle. The following are some relevant files you will find in ws-inout:

  • StockTrader.java, StockTraderHome.java, StockTraderEJB.java—. These are the back-end component stateless session bean files: remote interface, home, and bean implementation, respectively. The deployment descriptors for StockTrader, ejb-jar.xml, and weblogic-ejb-jar.xml are also found here.

  • StockTraderClient.java—. This file calls the buy service method.

  • build.xml—. This Ant build file compiles and runs the EJB compiler (ejbc) on the StockTrader bean and then invokes serviceGen to create the StockTrader.ear deployable Web service file. This file can also compile the client and run it.

Follow these steps to run the example:

  1. Copy all files under ws-inout to your local drive because you will need these files in a writeable location to run the example. You can locate this example anywhere in your drive.

  2. Edit the build.xml file and follow the instructions there for modifying certain property variables such as your WebLogic Server location.

  3. Open two DOS command prompts and call them DOS1 and DOS2.

  4. In DOS1, set up your environment to build and run the examples by invoking the WebLogic Server script (where wl_home is the root directory of WebLogic Platform 7.0):

    <wl_home>weblogic700samplesserverconfigexamplessetExamplesEnv.cmd
    
  5. In DOS1, use ant build to build the StockTrader EJB back-end component and the StockTrader Web service EAR file. It will automatically copy this EAR file to the WebLogic Server’s Examples server applications area in

    <wl_home>weblogic700samplesserverconfigexamplesapplications
    
  6. In DOS2, start the WebLogic Examples server by invoking the start script:

    <wl_home>weblogic700samplesserverconfigexamplesstartExamplesServer.cmd
    
  7. When the Examples server is up and running, use ant run in DOS1 to run the StockTraderClient.

  8. You can also go to a browser and bring up the StockTrader home page at

    http://localhost:7001/StockTrader/StockTrader
    

Try invoking the service to see the SOAP request and SOAP response, and note how the Holder classes are being serialized to XML. Are they? Why or why not?

InOut User-Defined Types

If you need to create a service which returns an output parameter that is not a built in type, you need to create a Holder class for your user-defined type. In most cases, WebLogic Server’s ServiceGen task will generate these holders for you. In some cases, however, you must edit a generated holder or choose to write one yourself. All the points discussed in “The Holder Interface” section earlier in this chapter apply to UDT holders. In addition, you must follow a naming convention for your class that implements Holder: Type Holder, where Type is the name of your user-defined type. For example, if your UDT is named Address, its Holder class is AddressHolder, as shown in Listing 30.11.

Example 30.11. Example of a Holder Class for the Address User-Defined Type

package examples.webservice.holders;
public final class AddressHolder implements javax.xml.rpc.holders.Holder
{
    public Address value;
    public AddressHolder() {
       // set to default initial value
    }
    public AddressHolder(Address addr) {
       value = addr;
    }
}

Remember that you still need to define your user-defined type, with its XML Schema definition, type mapping to serializers/deserializers, as well as the serializer/ deserializer classes themselves (or have them all generated by ServiceGen). Making a Holder class is an additional step. However, the ServiceGen task, by default, attempts to create Holder classes for your UDTs automatically and places them in the package <UDT-package>. holders, where <UDT-package> is the package of its corresponding user-defined type class. You can find these generated Holder classes in the WEB-INFclasses folder of the generated Web service WAR file, or in the client JAR file, if one is available (refer to Figure 30.6).

Writing Message Handlers

A SOAP message handler provides a mechanism for intercepting SOAP message requests (before passing on to any back-end component) and responses (before sending back to the service client). The infrastructure of handlers and handler chains is part of JAX-RPC 1.0, implemented faithfully in WebLogic Server 7.0.

Note

▸ For information on when handlers get invoked during a SOAP call, see “SOAP Handlers,” p. 1076.

Handlers

A handler is simply a Java class that implements the javax.xml.rpc.handler.Handler interface, possessing methods (among others) that can preprocess or process a SOAP request (handleRequest()), SOAP response (handleResponse()), or SOAP fault or exception (handleFault()). All methods in a handler are callbacks; the Web service container will call these methods at the appropriate times. You should not invoke any of these methods from within the Handler class. Figure 30.13 shows how server-side handlers work.

Server-side handler data flow with a back-end component.

Figure 30.13. Server-side handler data flow with a back-end component.

Note that the ensuing discussion pertains to server-side handlers only. Client-side handlers are not as commonly used and therefore not discussed, but they will have a different execution flow from what is shown here.

When handleRequest() is called, it has access to a SOAP request Msg that comes from the Web service client (unless it is part of a chain, discussed later). The handleRequest() method edits the SOAP message. When the method completes execution, the container then proceeds to invoke the back-end component, passing to it the possibly revised SOAP message Msg'. When the back-end component is complete, the container converts the result into a SOAP response Rsp and then invokes the handleResponse() method of the same handler instance. The XML SOAP response is accessible via the SOAPMessage and can be edited by handleResponse(). The possibly modified SOAP response Rsp' is then sent back to the client.

In the case of a Web service that consists exclusively of handlers and no back-end component, handleRequest() fulfills the request itself, and the response handler (not the container) must create the SOAP response, as shown in Figure 30.14.

Handler data flow without a back-end component.

Figure 30.14. Handler data flow without a back-end component.

To see a running example of this use case, see the example referenced by “The GenericHandler Class” section later in this chapter.

The Handler interface has the methods shown in Listing 30.12.

Example 30.12. The Handler Interface Methods

// life cycle methods
void init(HandlerInfo config);
void destroy();
// work methods
boolean handleRequest(MessageContext context);
boolean handleResponse(MessageContext context);
boolean handleFault(MessageContext context);
Qname[] getHeaders();

The methods are as follows:

  • init—. This method allows the handler object to initialize itself (for example, to obtain resources) and is guaranteed to be called before any handleXXX methods are invoked. The argument HandlerInfo represents configuration information for the handler. As you will see later, you can actually specify handler parameters in the web-services.xml file that you can access at init time through HandlerInfo.

  • destroy—. This method allows the handler object to do any cleanup, such as releasing resources, before the handler instance is discarded.

  • handleRequest—. The work of intercepting or modifying the SOAP request is done here. The Boolean return value matters only if there is a handler chain. Otherwise, it doesn’t matter what you return. This rule applies to all three handleXXX methods.

  • handleResponse—. The work of intercepting or modifying the SOAP response is done here.

  • handleFault—. If any exception is thrown while a handler or back-end component is executing, this method will be called.

  • getHeaders—. This method returns the header blocks that are recognized or will be processed by this Handler instance. This is part of a mechanism that enables the container to determine whether it is able to process a SOAP message, if the message has any mustUnderstand=true header blocks in the SOAP envelope (per SOAP 1.2 specifications). The container is supposed to poll each of the handlers via this getHeaders method to see if any of them recognize such required attributes. For now, you can just delegate this logic to the equivalent methods in HandlerInfo, as you will see in an example later. There is also a corresponding set method.

MessageContext, which is actually a SOAPMessageContext object, contains a collection of messages of type SOAPMessage, from which you can retrieve the XML SOAP message parts that you can read or edit. Figure 30.15 illustrates the relationship of these objects.

Relationship of handler objects.

Figure 30.15. Relationship of handler objects.

MyHandler is the custom Handler class that implements the javax.xml.rpc. handler.Handler interface. From SOAPMessageContext, you can make a series of calls to access any part of the SOAP message, both requests and responses, as shown in Figure 30.16.

Methods map for accessing SOAP message elements.

Figure 30.16. Methods map for accessing SOAP message elements.

The SOAP message classes shown in Figure 30.16 are all in package javax.xml.soap and are documented in the SOAP with Attachments API for Java (SAAJ) specifications (formerly part of Java API for XML Messaging, or JAXM), accessible from http://java.sun.com/xml/saaj.

Listing 30.13 shows a simple Handler class example. This sample is actually shipped as part of the examples bundle in WebLogic Server 7.0, in

<wl_home>weblogic700samplesserversrcexampleswebserviceshandlerlog

where wl_home is the base install directory for WebLogic Platform. The handler prints out the SOAP request and response messages, but it does not alter them in any way.

Example 30.13. A Sample Handler Class Implementation

package examples.webservices.handler.log;
import java.util.Map;
import javax.xml.rpc.handler.Handler;
import javax.xml.rpc.handler.HandlerInfo;
import javax.xml.rpc.handler.MessageContext;
import javax.xml.rpc.handler.soap.SOAPMessageContext;
import javax.xml.rpc.JAXRPCException;
import javax.xml.namespace.QName;
import weblogic.logging.NonCatalogLogger;
public final class LogHandler implements Handler
{
  private HandlerInfo handlerInfo;
  public void init(HandlerInfo hi) {
    handlerInfo = hi;
  }
  public void destroy() {}
  public QName[] getHeaders() { return handlerInfo.getHeaders(); }
  public boolean handleRequest(MessageContext mc) {
    SOAPMessageContext messageContext = (SOAPMessageContext) mc;
    System.out.println("** Request:" +
            messageContext.getMessage().toString());
    return true;
  }
  public boolean handleResponse(MessageContext mc) {
    SOAPMessageContext messageContext = (SOAPMessageContext) mc;
    System.out.println("** Response: " +
            messageContext.getMessage().toString());
    return true;
  }

  public boolean handleFault(MessageContext mc) {
    SOAPMessageContext messageContext = (SOAPMessageContext) mc;
    System.out.println("** Fault: " +
           messageContext.getMessage().toString());
    return true;
  }
}

This code should be self-explanatory. The passed-in MessageContext parameter in the handlerXXX methods is actually a SOAPMessageContext object and needs to be typecast.

The GenericHandler Class

GenericHandler is a convenience class offered by WebLogic Server to implement the Holder interface. It contains default implementations of many required housekeeping methods so that your custom Handler class needs to override only the handleXXX methods. To use GenericHandler, extend it as shown in Listing 30.14.

Example 30.14. Using the Convenience GenericHandlerClass

Public myHandler extends GenericHandler {
   Public Boolean handleRequest(..) {
     /* process the request */
   }
   public Boolean handleResponse(..) {
      /* process the response */
   }
   public Boolean handleFault(..) {
      /* process the fault */
   }
}

You can build and run a WebLogic Server example that uses the GenericHandler class; this example uses SOAP handlers exclusively with no back-end component.

Handler Chains

For finer control and more effective error handling, you can actually delegate the SOAP processing task to multiple Handler classes in a chain. You not only define what particular Handler classes constitute a chain but also the order in which each handler is invoked. Figure 30.17 shows a chain made up of three Handler classes—HandlerA, HandlerB, and HandlerC, defined in that order—and a back-end component, which together work in concert to fulfill a service request.

The order of invocation in a handler chain.

Figure 30.17. The order of invocation in a handler chain.

Note

The handlers are invoked in definition order during a request and in the reverse definition order during a response. Also, a handler gets the modified SOAP message from its predecessor handler.

Flow of Control

You can dynamically change the control flow among handlers in a chain. That is, you are not bound to always execute every handler in a chain, every time. For instance, in Figure 30.17, HandlerB might decide at execution time to entirely fulfill the request itself or determine that it should not proceed for some reason, such as an exception occurring. Figure 30.18 shows the typical flow of control in a handler chain. All handlers normally return true.

Typical handler chain execution.

Figure 30.18. Typical handler chain execution.

Between steps 4 and 5 the container would have created an XML SOAP response from the back-end component’s resultset.

So, how can the chain be “blocked” at will? The answer lies in the following three rules:

  1. If a handler in a chain returns true, execution proceeds to the next handler or back-end component.

  2. If a handler (for example, HandlerB) returns false, however, the chain becomes blocked and the next handler, including the back-end component, is no longer invoked. Instead, that same blocking handler’s (HandlerB’s) handleResponse() method is invoked. From then on, all other preceding handler’s handleResponse() methods will also be invoked.

  3. If a handler throws an exception, the chain also becomes blocked, and the next handler, including the back-end component, is no longer invoked. Instead, the same blocking handler’s handleFault() method is invoked. From then on, all other preceding handler’s handleFault() methods will also be invoked.

    Figure 30.19 illustrates the case of Rule 2 in the preceding list.

    How a false-blocking handler changes control flow.

    Figure 30.19. How a false-blocking handler changes control flow.

In this case, who initially creates a SOAP response? The answer is either in step 2 (HandlerB.handleRequest) or step 3 (HandlerB.handleResponse), whichever is more appropriate. The two methods can either use private variables to collaborate or use a shared context, discussed later. In general, if a handler in a chain can potentially alter control flow, that handler should be able to create a specific SOAP response for that blocking situation.

Figure 30.20 illustrates the case of Rule 3.

How an exception-blocking handler changes control flow.

Figure 30.20. How an exception-blocking handler changes control flow.

If any exception is thrown in a handler, the container will invoke that handler’s handleFault() method. In this case, the container will create the SOAP response in the shared context. If, in step 3, HandlerB.handleFault() returned false, it would block fault chain processing, and henceforth no other handleFault() methods would be called.

Shared Context

Handlers in a chain can communicate with each other through the SOAPMessageContext parameter. We have described SOAPMessageContext only as a way to retrieve the SOAP message, but it is also a place where arbitrary key-value pairs, or properties, can be stored and retrieved. That is, it can be used by handlers in a chain to share processing-related state. The property set methods (inherited from MessageContext) are available for this purpose, as shown in Listing 30.15.

Example 30.15. MessageContext Property Set Methods

package javax.xml.rpc.handler;
public interface MessageContext {
   void setProperty(String name, Object value);
   Object getProperty(String name);
   void removeProperty(String name);
   boolean containsProperty(String name);
   java.util.Iterator getPropertyNames();
}

HandlerA can invoke msgContext.setProperty("user", "John Doe") and HandlerB can retrieve that information by invoking msgContext.getProperty("user").

Configuring Handlers into web-services.xml

To instruct your Web service to utilize your Handler classes, you need to perform the following two steps:

  1. Define your handler and handler chain(s). If you have only a single handler, you still need to define a handler chain, albeit with only one handler.

  2. Specify that a service operation use the handler chain, either exclusively or in addition to a back-end component.

Defining Your Handlers and Handler Chains

As Figure 30.11 shows, the handler chains section of the web-services.xml file comes just before the Web service section. A typical handler chain specification is shown in Listing 30.16.

Example 30.16. A Sample Handler Chain Definition in web-services.xml

<handler-chains>
   <handler-chain name="myChain">
     <handler class-name="myHandlers.HandlerA" >
         <init-params>
            <init-param name="debug" value="on" />
         </init-params>
     </handler>
     <handler class-name="myHandlers.HandlerB" />
     <handler class-name="myHandlers.HandlerC" />
   </handler-chain>
</handler-chains>

The statements in Listing 30.16 define only one handler chain, but multiple chains can be defined with repeating <handler-chain> elements. Each <handler> subelement declares a Handler class via the fully qualified Handler class name in the attribute class-name. The <init-params> subelement defines parameters for configuring HandlerA; this information is available to the Handler class at initialization time via the init() method argument HandlerInfo. The order of the <handler> subelements determines the order of handler execution.

Specifying Handler Use

When you define a Web service operation and specify its back-end component, you can also specify the use of handlers. Listing 30.17 shows a typical operation definition but with reference to the use of the myChain handler declared earlier.

Example 30.17. Specifying the Use of Handlers in an Operation Definition

<operations>
   <operation name="getQuote"
            method="getQuote"
            component="myEJB"
            handler-chain="myChain" />
</operations>

The statement in Listing 30.17 specifies that when operation getQuote is invoked, the handler chain myChain should first be executed prior to invoking the getQuote method of the myEJB back-end component (defined in the Components section of web-services.xml, not shown). To define a handler chain–only service with no back end component, simply omit the component and method attributes in operation.

Understanding Exception Handling

What if business logic in the back-end component throws an exception? How does the container handle such exceptions? First, it is recommended that your Web service’s external exceptions be mapped to unique SOAP fault codes. External exceptions are those that can be thrown back to the client when an exposed service operation is invoked. Exceptions that may be thrown internally to the service logic do not need to be mapped, just the external ones. Then you should strive to throw SOAPFaultException only from your Web service methods, containing the unique fault code, instead of any user-defined or built-in exceptions. In the SOAPFaultException, a constructor looks like this:

public SOAPFaultException (javax.xml.namespace.QName faultcode,
                           String faultstring,
                           String faultfactor,
                           javax.xml.soap.Detail detail);

The faultcode parameter is the fault code you created to represent a particular business exception. Fault codes, by SOAP specifications, are two-part strings of "SOAP-fault-code.user-code". Because this input fault code string will become part of an XML SOAP message, it may need to be namespace-qualified; hence, it is of type QName. The other parameters help expound on the fault code, as described in the SOAP 1.2 specifications.

The following statement throws a new instance of SOAPFaultException, with a fault code of "Client.InvalidID" in namespace www.bea.com/ws (two arguments to the QName constructor):

throw new SOAPFaultException (new QName(www.bea.com/ws, "Client.InvalidID"),
                             "Employee ID must be numeric",
                             "urn:UserManagement", null);

The WebLogic Web service container takes a SOAPFaultException thrown by a back end component and serializes it into the SOAP response message. On the client side, the <fault> element is deserialized back into a SOAPFaultException. It actually throws a JAXRPCException to the client, with the SOAPFaultException embedded in it.

One distinct advantage of using SOAPFaultException (or one that extends it) exclusively is that your list of possible fault codes for each service method can be documented in the service WDSL file. That way, a client has a more complete description of the service and can program more effective error reporting or recovery mechanisms.

If your back-end component throws any other exception, the WebLogic container will attempt to map it to a SOAPFaultException as best it can. It cannot, however, embed your exception into a JAXRPCException and throw it on the client side because your client may not have access to your exception class definition. SOAPFaultException, on the other hand, is always available to a WebLogic Web service client.

Writing Asynchronous Web Services

As Figure 29.15 in Chapter 29 shows, you can create asynchronous Web services by using JMS destinations (queues) as back-end components. These queues act as temporary buckets for holding messages, be it input data or a return value. An object that puts a message on a queue is called a producer, and one that receives a message is called a consumer. Although a JMS destination is considered as a back-end component for a Web service, these destinations cannot execute business logic. A listener object such as a message-driven bean (MDB) or a Java class that listens and posts to these queues is needed to house business logic to process service requests.

This section assumes that you are generally familiar with Java Messaging Service and message-driven bean concepts.

Note

▸ For more information on these topics, see Part V, “Developing Business Logic—Enterprise JavaBeans,” p. 607.

JMS and Message-Driven Beans Example

The two types of JMS destinations are queues and topics. Because the use of JMS topics is deprecated in WLS 7.0, we will discuss only queues. A JMS queue implements point-to-point messaging, where a message is guaranteed to be delivered to only one consumer. The producer places a message in a queue, and a consumer who is subscribed to the queue (also called a listener) retrieves it. Perhaps these concepts are best explained in the context of an example that is provided on our Web site. Figure 30.21 shows the components in this example.

Players in a JMS-implemented Web service.

Figure 30.21. Players in a JMS-implemented Web service.

In this sample, the client is split into two separate programs:

  • SubmitClient—. Invokes the submitData Web service operation, passing it data to be processed

  • QueryClient—. Invokes the requestData operation, retrieving the results of invoking submitData

As you can see, the JMS producer for destination inqueue is actually the SOAP servlet for the submitData operation. The JMS consumer for inqueue is the listener ProcessorMDB, a message-driven bean that is invoked to process the service data. ProcessorMDB then places its processing results in outqueue, the requestData operation’s JMS queue. That is, ProcessorMDB is both a consumer (of inqueue) and a producer (to outqueue).

Like any message-driven bean, ProcessorMDB must implement an onMessage() method and subscribe to inqueue as a listener. When a message is produced in inqueue, the container automatically makes available an instance of ProcessorMDB (it instantiates one or gets one from the pool) and invokes its onMessage() method. The business logic for processing the input data to the submitData operation is embodied in the onMessage() method of ProcessorMDB.

This sample incorporates a very simple use case:

  1. SubmitClient calls the submit operation, passing it a string. The message type of JMS destination inqueue is java.lang.String (this is specified in the serviceGen Ant task of Listing 30.21).

  2. ProcessorMDB takes the input string, prefixes another string to it, and then posts the new concatenated string to outqueue.

  3. QueryClient calls the query operation (with no arguments) and gets back the concatenated string.

The ProcessorMDB Bean

Now look at the onMessage() method of this bean, shown in Listing 30.18.

Example 30.18. Business Logic in the onMessage Method

public void onMessage(Message msg) {

  // WebLogic JMS-backed service always uses ObjectMessage
2 ObjectMessage om = (ObjectMessage) msg;

  try {
    String text = (String) om.getObject();

    // Get connection & start it
8   Context ctx = getInitialContext();
    QueueConnectionFactory factory = (QueueConnectionFactory)
       ctx.lookup("weblogic.jms.ConnectionFactory1");
    Queue queue = (Queue) ctx.lookup("weblogic.jms.outqueue");

13  QueueConnection connect = factory.createQueueConnection();

    QueueSession session = connect.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);

17  QueueSender sender = session.createSender( queue );

19  connect.start();

    // Post the revised message in outqueue
    ObjectMessage outMsg = session.createObjectMessage();
23  outMsg.setObject(text + " stays mainly in the Plain.");
    sender.send(outMsg);
    connect.close();
  }
  catch(JMSException ex) {
    ex.printStackTrace();
  }
  catch (NamingException ne) {
    ne.printStackTrace();
  }
}

Lines 8 to 19 are necessary steps for creating a connection to outqueue (line 13) and for configuring this bean to be a producer to outqueue (line 17). Line 23 simulates business logic processing of the input message by merely appending "stays mainly in the Plain" to the presumed input string of "The Rain in Spain" to complete the adage: “The Rain in Spain stays mainly in the Plain.”

Note in line 2 that the JMS message type used here is ObjectMessage (as opposed to TextMessage or MapMessage), which is required in a WebLogic JMS-backed Web service.

If you want to tell the WebLogic container that this bean is to be a consumer or listener to inqueue, you do so in the bean’s deployment descriptor. Listing 30.19 shows the ejb-jar.xml file for ProcessorMDB.

Example 30.19. Deployment Descriptor (ejb-jar.xml) for ProcessorMDB

<ejb-jar>
 <enterprise-beans>
    <message-driven>
      <ejb-name>exampleMessageDriven1</ejb-name>
      <ejb-class>examples.webservices.message.ProcessorMDB</ejb-class>
      <transaction-type>Container</transaction-type>
      <message-driven-destination>
8       <destination-type>javax.jms.Queue</destination-type>
      </message-driven-destination>
    </message-driven>
 </enterprise-beans>
</ejb-jar>

Line 8 declares that this bean will be a listener (consumer) for a JMS destination of type javax.jms.Queue. But this is not enough because the container still does not know exactly which queue instance it is to listen on. So, in the WebLogic-specific deployment descriptor file weblogic-ejb-jar.xml shown in Listing 30.20, you must specify a particular queue instance.

Example 30.20. WebLogic-Specific Deployment Descriptor (weblogic-ejb-jar.xml) for ProcessorMDB

<weblogic-ejb-jar>
  <weblogic-enterprise-bean>
    <ejb-name>exampleMessageDriven1</ejb-name>
    <message-driven-descriptor>
      <pool>
        <max-beans-in-free-pool>200</max-beans-in-free-pool>
        <initial-beans-in-free-pool>20</initial-beans-in-free-pool>
      </pool>
9     <destination-jndi-name>weblogic.jms.inqueue</destination-jndi-name>
    </message-driven-descriptor>
    <jndi-name>examplesMessageDriven1</jndi-name>
  </weblogic-enterprise-bean>
</weblogic-ejb-jar>

Line 9 specifies the JNDI name of the inqueue destination to listen on. Before you can deploy this Web service, you must configure a few objects in the Examples server, described next.

Setting Up JMS Factory and Destinations

Both JMS destinations inqueue and outqueue need to be defined in the server instance where this asynchronous Web service is deployed. In addition, a JMS connection factory needs to be defined. Follow the steps in the following lists to perform these tasks in the WebLogic Administration Console (for example, go to http://localhost:7001/console).

Define the JMS connection factory by following these steps:

  1. A JMS connection factory with JNDI name weblogic.jms.ConnectionFactory may already exist in the Examples server instance. To check, click these nodes in the left pane of the Administration Console: examples, Servers, examplesServer. Right-click and then select View JNDI Tree. In the new pop-up window, see whether you can follow examplesServer, weblogic, jms, ConnectionFactory. If so, you don’t need to perform the ensuing steps.

  2. Click examples, Services, JMS node; then right-click the ConnectionFactories node and choose Configure a New JMSConnectionFactory from the drop-down list.

  3. Enter a name for the connection factory in the Name field.

  4. Enter the JNDI name weblogic.jms.ConnectionFactory, which the example assumes.

  5. Click Create.

  6. Click the Targets tab and move the name of the server host (for example, examplesServer) to the Chosen list box, if it is not already there.

  7. Click Apply. You have now successfully defined a JMS connection factory.

Define a JMS destination (queue) by following these steps:

  1. Choose examples, Services, JMS, Servers, examplesJMSServer. Then right-click Destinations and choose Configure a New JMSQueue.

  2. Enter inqueue as the name of the queue.

  3. Enter weblogic.jms.inqueue as the JNDI name.

  4. Leave the default values for all other fields, especially the Enable Store field.

  5. Click Create.

  6. Repeat these steps to define the output queue, with the name outqueue and JNDI name weblogic.jms.outqueue.

Running the Asynchronous Web Service Sample

You are now ready to build and run this asynchronous Web service example:

  1. Copy all files under ws-async to your local drive because you will need these files in a writeable location to build and run the example. For these sample build scripts to work, you must copy ws-async to the WebLogic Server examples location as follows:

    <wl_home>weblogic700samplesserversrcexampleswebservicesws-async
    
  2. Start three DOS command shells, and call them DOS1, DOS2, and DOS3.

  3. In the DOS1 and DOS2 command shells, set up your environment to build and run samples by invoking the WebLogic Server script (where wl_home is the root directory of WebLogic Platform 7.0):

    <wl_home>weblogic700samplesserverconfigexamplessetExamplesEnvironment.cmd
    
  4. In the DOS1 shell, use ant to build the ProcessorMDB EJB and the asynchronous Web service enterprise application folder jms_send_queue. It automatically copies this application to the WebLogic Server’s Examples server applications area:

    <wl_home>weblogic700samplesserverconfigexamplesapplicationsjms_send_queue
    
  5. In the DOS3 command shell, start the WebLogic Examples server by invoking its start script:

    <wl_home>weblogic700samplesserverconfigexamplesstartExamplesServer.cmd
    
  6. In the DOS1 shell, use ant run-producer to start the SubmitClient producer client that submits the following string data: "The Rain in Spain". In DOS3, ProcessorMDB will emit messages saying its onMessage() method is being called and will display the received input string from SubmitClient.

  7. In the DOS2 shell, use ant run-consumer to start the QueryClient consumer client polling for results. It should be able to retrieve the results and print out the following: “QueryClient got message: The Rain in Spain stays mainly in the Plain.

Examining How the Asynchronous Sample Is Built

If you look at the Ant build file ws-asyncuild.xml, you can see that the WebLogic Ant task ServiceGen is used to assemble the asynchronous Web service. The invocation is shown in Listing 30.21.

Example 30.21. ServiceGen Invocation to Assemble an Asynchronous Web Service

1   <servicegen  destEar="${source}jms_send_queue"
                 contextURI="WebServices" >
        <!-- PRODUCER WEB SERVICE DEFINITION -->
4       <service JMSDestination="weblogic.jms.inqueue"
                 JMSAction="send"
                 JMSDestinationType="queue"
                 JMSConnectionFactory="weblogic.jms.ConnectionFactory1"
                 JMSOperationName="submit"
                 JMSMessageType="java.lang.String"
                 generateTypes="True"
                 targetNamespace="http://tempuri.org"
                 serviceName="SubmitService"
                 serviceURI="/SubmitService"
                 expandMethods="True">
                 <client packageName="examples.message" useServerTypes="true" />
        </service>
        <!-- CONSUMER WEB SERVICE DEFINITION NOT SHOWN -->
    </servicegen>

Line 1 invokes servicegen, specifying that the Web service be built into an enterprise application folder (as opposed to an EAR file) called jms_send_queue.

Line 4 defines a new Web service. Because JMS attributes are specified in this <service> element, it will be built as a JMS-backed Web service. JMSDestination specifies the JNDI name of the JMS queue or topic to which this service is either a consumer (if JMSAction is receive) or producer (if JMSAction is send). JMSDestinationType tells ServiceGen whether the back-end JMS destination is a queue or topic. For the container to connect to the back-end JMS destination, a connection factory must be used. Therefore, JMSConnectionFactory points to the JNDI name of this factory. When you build a JMS-backed Web service using ServiceGen, only one operation is allowed. JMSOperationName specifies the name of this lone operation, which takes only one parameter, of type JMSMessageType. This parameter specifies the data type of the message that is being placed into the JMS destination.

In the case of an asynchronous, receive type of Web service (if JMSAction is receive), the lone operation is assumed to take no parameters, and its return type is determined by the value of JMSMessageType.

Notice that nowhere do you specify the message-driven bean to ServiceGen. Remember that this is a JMS-backed Web service, so ServiceGen is oblivious to any processing that happens beyond the JMS messaging activities. In other words, as far as the container is concerned, the Web service is fulfilled after the message is placed in its appropriate JMS destination. Unfortunately, this fact has some implications:

  • You must insert these business message-driven beans to the Web service enterprise application (EAR file or expanded) in the form of one or more JAR files at the top or root-level location (alongside the WAR file; see Figure 30.6 in this chapter).

  • The META-INF/application.xml file must be updated to deploy any of your message-driven bean JAR files.

  • Any exceptions thrown by the message-driven bean might not be propagated back as a SOAPFaultException.

You deploy your MDB component as you would do any other J2EE enterprise application—in the application.xml file, as shown in the following code:

<application>
  ...
 <module>
   <ejb>myMDB.jar</ejb>
 </module>
  ...
</application>

You can place both a send and receive operation into one asynchronous Web service, but you must then build the Web service manually. The ServiceGen task syntax does not allow you to specify more than one operation in a JMS-backed Web service. The next section shows you how to create an asynchronous Web service with multiple operations.

Using the Asynchronous Web Service with Multiple Operations

The most expedient way to create an asynchronous Web service is to use ServiceGen to generate an enterprise application in expanded form (as opposed to an EAR file), by specifying a folder name in the destEar attribute rather than an EAR file, as in Line 1 of Listing 30.21. This produces a web-services.xml file inside the embedded WAR file (see Figure 30.6). You can then unjar the Web service WAR file to edit the web-services.xml file, and add more <operation> elements and their JMS <components>. Listing 30.22 shows an abbreviated web-services.xml file that defines two JMS-backed operations.

Example 30.22. A Sample Web Service Definition with Multiple JMS-Backed Operations

<web-services>
  <web-service ..>
     <components>
        <jms-send-destination name="inqueue"
              connection-factory="weblogic.jms.ConnectionFactory">
        </jms-send-destination>
        <jms-receive-queue>
              connection-factory="weblogic.jms.ConnectionFactory">
        </jms-receive-queue>
     </components>
     <operations ..>
        <operation invocation-style="one-way" name="submit" component="inqueue" >
        </operation>
        <operation invocation-style="request-response" name="query"
             component="outqueue" >
        </operation>
     </operations>
  </web-service>
</web-services>

After editing the web-services.xml file, you need to jar it up again in the Web service’s WAR file. Any new EJB JAR files need to be added into the enterprise application top-level or root directory (the same level as the Web service’s WAR file).

Securing Your Web Services

There are several ways of protecting your WebLogic Web services:

  • Encrypted communications—All communication and data traffic between the client and Web service provider are encrypted via the HTTPS transport to prevent anyone from snooping on your conversations.

  • Authentication—This approach identifies the client and server entities to ascertain that they are who they say they are. This is done through SSL (Secure Sockets Layer) and almost always in conjunction with HTTPS. Credentials presented in this step are in the form of electronic certificates that are issued by a Certificate Authority (CA). Authentication can be either one-way (the server authenticates to the client by sending its credentials to the client) or two-way (the client and server authenticate and send credentials to each other).

  • Authorization—This approach primarily deals with whether the client has sufficient permission to access a Web service. Credentials here can be digital signature objects or, more typically, username/password combinations.

You can choose to implement any or all of these techniques, and in any combination. You can set authorization for WebLogic Web services in varying degrees; they are listed here in order from most restrictive to least restrictive:

  • Secure the entire Web service by securing the Web service URL. This prevents unauthorized access not only to Web service invocation, but also to browsing its home page and its WSDL file.

  • Secure the business logic behind the Web service. This allows public access to the Web service home page and WSDL, but for users to invoke the service, they must submit credentials. In WebLogic Server, however, only EJBs (some or all of its methods) can be secured. If your Web service also has Java class, handler, or JMS destination back-end components, such business logic will not be protected. The only way to protect them is to secure the service URL.

Figure 30.22 attempts to guide you through the process of deciding what security measures to implement for your Web service.

Deciding which Web service security features should be used.

Figure 30.22. Deciding which Web service security features should be used.

The following sections explain each task shown in Figure 30.22.

Using SSL

When using SSL, the client may need to send credentials to authenticate itself to the server. Before a Java client can invoke such a secure Web service, it must configure the following items:

  • Identify the location of its trusted certificates

  • Identify the location of its WebLogic license file

  • Define the Java protocol handler to be used

These tasks may be accomplished programmatically or through JVM arguments (system properties). A sample Java client startup command is shown here, for a Java program named TraderClient, using system properties, to invoke an SSL-secured Web service:

java -Dweblogic.webservice.client.ssl.trustedcerts=c:democert.pem
     -Dbea.home=c:ea
     -Djava.protocol.handler.pkgs=weblogic.webservice.client TraderClient

Restricting Your Web Service to Use HTTPS

By default, all WebLogic Web services are accessible through either HTTP or HTTPS (if your WebLogic Server is configured for SSL). To preclude the use of HTTP (that is, force all service calls to use HTTPS), specify the following attribute in the ServiceGen Ant task (the line shown in bold):

<servicegen ... >
   <service ejbJar="traderBean.jar"
               protocol="https"
               serviceName="TraderService"
   ...
   </service>
</servicegen>

Or in the web-services.xml file (only if you are manually building the Web service), specify the following attribute (the line shown in bold):

<web-service name="Trader"
             targetNamespace="www.bea.com/Trader";
             protocol="https"
             uri="/TraderWS" ... >
...
</web-service>

Remember though that using HTTPS will slow down the Web service communication layer considerably due to the added steps of encrypting and decrypting the SOAP messages.

Securing Your Web Service URL

You can protect your entire Web service (home page, WSDL file, service invocations) by securing the service URL. ServiceGen cannot do this for you, so you must secure it manually after the EAR folder is generated. You need to edit your Web service Web application file, web.xml, located in the Web application WAR file <ear-root><war-name>. war, where <ear-root> is the top-level directory of the EAR folder, and <war-name> is the WAR filename you specified when you invoked ServiceGen (attribute warName; otherwise, it defaults to filename web-services.war). First, you need to extract the file <war-root>WEB-INFweb.xml from the WAR file (see Figure 30.6 earlier in this chapter). Then you add a security constraints section, as shown in Listing 30.23.

Example 30.23. Securing a Web Service URL

<web-app>
    ...
    <security-constraint>
      <web-resource-collection>
        <web-resource-name>TraderService</web-resource-name>
        <description>The Trader Web Service</description>
        <url-pattern>/TraderService</url-pattern>
        <http-method>GET</http-method>
        <http-method>POST</http-method>
      </web-resource-collection>
   </security-constraint>
   ...
</web-app>

Notice the URL pattern being protected; this is the Web application URI specified in the serviceURI attribute of the ServiceGen <service> element (not the contextURI attribute of the <servicegen> element).

When the URL is protected, a username and password will be required to access the service. If you try to access the Web service home page, a login dialog box will be displayed. To access the Web service programmatically from a Java client, you must include the following statements (static client style) in the client code. These statements define credentials to be passed along with the service request, as shown in the following code:

TraderService_Impl ts = new TraderService_Impl();
TraderServicePort trader = ts.getTraderServicePort(";UserName";, "Password");
trader.buy("BEAS");

Securing Your EJB Back-End Component

The unique characteristic of this security measure is that your potential service clients can still read about your Web service (home page or WSDL file), although they may not be able to invoke any operation that is backed by an EJB method. Because security is affected at the method level, you can be selective and restrict, for example, only methods that have side effects or that update functionality.

An EJB is secured via its deployment descriptor, both in the ejb-jar.xml and weblogic-ejb-jar.xml files.

ejb-jar.xml

In the <assembly-descriptor> element of the ejb-jar.xml file, you can list the security roles that can access this EJB, and in particular, the methods that can be invoked by each security role, as shown in Listing 30.24.

Example 30.24. Specifying What Security Roles Can Access What EJB Methods

<ejb-jar>
  ...
  <assembly-descriptor>
    ...
    <security-role>
       <description>Gold Member Clients</description>
       <role-name>GoldStatusRole</role-name>
    </security-role>
    <method-permission>
       <description>Gold members can buy & sell</description>
       <role-name>GoldStatusRole</role-name>
       <method>
          <ejb-name>TraderBean</ejb-name>
          <method-name>buy</method-name>
          <method-name>sell</method-name>
       </method>
    </method-permission>
    ...
  </assembly-descriptor>
  ...
</ejb-jar>

weblogic-ejb-jar.xml

The <security-role-assignment> element of the weblogic-ejb-jar.xml file can then be used to assign the roles just specified to specific users defined in WebLogic, as shown in Listing 30.25.

Example 30.25. Specifying What Users Belong to What Security Role

<weblogic-ejb-jar>
   ...
   <security-role-assignment>
      <role-name>GoldStatusRole</role-name>
      <principal-name>gold_customer</principal-name>
   </security-role-assignment>
   ...
</weblogic-ejb-jar>

Debugging Your WebLogic Web Services

After you have deployed your WebLogic Web service, there are a number of ways you can ensure that it is working properly.

Checking Out the Web Service Home Page

If you are able to bring up the Web service home page, chances are the Web service has been successfully deployed. On the home page, make sure that all the operations you intended are listed. Try invoking the service by clicking each operation name link. It is also important to click the WSDL file link and examine the WSDL file that is displayed. Make sure the service endpoint is correct. See Chapter 29 for more information on Web service home pages.

Examining the Web Service Through the WebLogic Administration Console

You can see the Web services components listed as deployed in the WebLogic Administration Console. After you invoke this console (for example, http://localhost:7001/console for the Examples server), go to examples, Servers, Deployments, Web Service Components. Figure 30.23 shows the console with these nodes expanded (refer to arrow D).

WebLogic Administration Console showing Web service components.

Figure 30.23. WebLogic Administration Console showing Web service components.

Recall that a WebLogic Web service is composed of the following J2EE components:

  • Web application (WAR file)

  • EJB back-end components (JAR file)

Now look for the Web services that are deployed and their WAR and JAR files. In Figure 30.23, you can see that two Web service applications are deployed, in the Application column of the right pane (arrow B). These are mangled application names derived from the actual Web service EAR file or folder name. The Name column (arrow A) of the same table shows their WAR files. You can see their JAR files, if any, by clicking (in this example) examples, Servers, Deployments, Applications, _appsdir_jms_send_queue_dir, where you will find both the Web service’s WAR and JAR files (arrow C).

Looking at the Generated web-services.xml File

If you are familiar with the format of the web-services.xml file, you can look at this generated file to ascertain that you have correctly defined your Web service characteristics to the ServiceGen task. You can see the serializer and deserializer classes that are being used during service invocation, as well as any user-defined type definitions.

Runtime Diagnostics

You can cause the SOAP requests and responses to be displayed on either the client or server command prompts. To do so, specify the following system property when invoking your Java client:

java -Dweblogic.webservice.verbose=true yourPackage.YourJavaClient

To see the SOAP messages on the server side, specify the same system property when starting WebLogic Server, as follows:

java -Dweblogic.webservice.verbose=true ... weblogic.Server

Again, doing this shows the output of the server-side handlers, if any.

WebLogic Log Files

Sometimes stack traces and exception messages may not show up on the command console where WebLogic Server was started. This is the time to check the log files that WebLogic Server keeps on every client request and component execution. For the Examples server, you can check out the following log files:

<BEA-HOME>weblogic700samplesconfigexamplesexamplesServerexamplesServer.log

<BEA-HOME>weblogic700samplesserverconfigexampleslogswl-domain.log

Note

These log files are appended to continually, even after a server is restarted. You should always scroll to the bottom for the latest information and page up from there. You can also see logs using the WebLogic Administration Console, which is the preferred method. After invoking the Administration Console (for example, http://localhost:7001/console for the Examples server), go to examples, Servers, examplesServer. Right-click examplesServer and select View Server Log. Note that the right pane’s log view defaults to show only 500 lines. You can customize the view to show more lines to see the entire log.

Best Practices

This section delves into some tips and advice about the design and implementation of WebLogic Web services. As with any evolving technology, the topic of “best practices” is in the process of constant discovery and refinement. These guidelines work well primarily for extra-enterprise (for outside client consumption) or inter-enterprise (between two disparate enterprises or partner companies) Web services. For intra enterprise Web services, some of these rules may be relaxed.

Employing Coarse-Grained Interactions

One of the basic tenets of Web services is that they should employ coarse-grained interactions. This point is akin to the EJB design principle that entity beans are inherently fine-grained and hence should be fronted by session beans. In other words, a Web service interaction should be embodied in as few operation calls as possible. This approach has several advantages:

  • It reduces network traffic, which optimizes performance.

  • Fewer operation invocations can simplify exception handling.

Remember to export services, not your application components. You should not be able to detect your component architecture through the Web service. Why? First of all, doing so would almost inevitably make your Web service too fine-grained. Second, you don’t want your Web service to be too tightly coupled with your component architecture. That way, your internal system architecture can change and evolve without necessarily affecting your published Web services. If possible, create a separate “front,” or layer of wrapper code (maybe EJBs), between your components and the Web service. This greatly increases flexibility and reduces fragility. Remember, your service description or WSDL will tend to outlive your application components.

Your Web service should offer interactions at a business level, utilizing the business vocabulary for the particular domain. In fact, the engineers and architects who are building such services may not be the best people to design the service interactions (operations); perhaps a business or corporate developer is more fitting. Incidentally, that is the goal of WebLogic Workshop: to empower business professionals—that is, non-programmer types—to create enterprise Web services.

Promoting Loose Coupling

Loose coupling means that the client should not in any way have knowledge or dependence on how a service is implemented. Also, a client’s interaction with the service should be within the confines of a published or public interface—no secret backdoors or clandestine agreements (especially error codes or function codes). Presume nothing about your client or server, except what is published in a service description, like a WSDL file. Now that we’ve said that, the truth is that WSDL files today do not carry enough semantic information, so some presumptions must be made; for instance, you can only deduce that a service named StockTrader with an operation called buy must be a request to buy shares of company stock.

Deciding What Service Mode Is Important

Synchrony or asynchrony in a Web service solution can make or break the model. In general, you should use a synchronous (RPC) Web service when its operation response or result is necessary for the client process or execution to continue. Asynchronous interactions are generally more natural for application-to-application communications because they can hide mismatches in the performance and availability of the communicating systems. Asynchrony also tends to localize interaction failures; because timing is not an issue, there is likely no “Domino Effect.” Therefore, asynchrony is usually necessary in cases where there are complex, multi-party interactions or transactions.

Avoiding Method Overloading

The SOAP specification distinguishes operations only by name, without regard for their signatures. Whereas Java allows two methods of the same name to co-exist if they have different argument lists, a SOAP message or WSDL file cannot distinguish between the two. This is not to say that your Web service back-end Java components cannot have overloaded methods. It can; you just cannot expose all such overloaded methods without having to rename them for uniqueness in the web-services.xml file. You can also select one particular overloaded method for inclusion into the Web service without renaming it by specifying both the method name and its signature when mapping the method to an operation in the web-services.xml file.

Knowing That Web Services Are Not Always the Answer

Web services do not always provide the solution you’re seeking either. More well established distributed computing technologies such as RMI, JMS, or CORBA are still relevant and, in fact, solve many problems that Web services cannot yet address. A Web service is not meant to replace all binary protocols. The biggest claim about Web services thus far is that they can tie together totally disparate and incompatible systems, regardless of their programming language or platform legacies.

New Features in WLS 7.0 Service Packs

This section briefly discusses some new tools that ship with WLS 7.0 Service Pack 1. These new tasks were already being performed as part of ServiceGen or ClientGen but are now separately executable for added flexibility. Figure 30.24 shows what they do.

Miscellaneous Ant tasks to facilitate building Web services.

Figure 30.24. Miscellaneous Ant tasks to facilitate building Web services.

The following list describes what these tasks do:

  • wsdl2Service—. Takes a WSDL file and creates a WebLogic Web service deployment descriptor file and back-end component skeletons. Use this task if you need to build a Web service based on a known WSDL description. The skeletons are Java source templates that partially implement the Web service for you to add business logic and processing to complete the service.

  • source2wsdd—. Takes your back-end Java source files and produces a Web service deployment descriptor for you to assemble a Web service manually.

  • wspackage—. Aids in assembling the many different Web service components that you may have built using other Ant tools into a WebLogic Web service (J2EE enterprise application). This task nicely obviates the need for you to know exactly where certain components are supposed to go in the EAR, or to run the jar utility.

Another versatile Ant task is autotype, for all your user-defined data type authoring needs. Figure 30.25 depicts what autotype can do.

The many capabilities of the autotype utility.

Figure 30.25. The many capabilities of the autotype utility.

Summary

This chapter walked you through the many options WebLogic Server offers for authoring your Web service provider. Both WebLogic Server and our Web site provide many live samples to confirm your understanding of this material, and we strongly encourage you to run them so that you get the feel of Web service authoring. We have tried to guide you from Web service conception, design, and security to development, deployment, and debugging. Some design guidelines were distilled and presented to help you build more effective as well as maintainable Web services. In the next chapter, you will discover even more details about Web services.

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

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