Implementing Clients

Web service clients can be built on any platform in any language, so long as they adhere to the existing standards for invoking web services. In this section, we look at how to use WebLogic's JAX-RPC implementation to build Java clients that can invoke a web service. WebLogic lets you generate a web service-specific client JAR that can be used by clients to statically invoke the operations of the web service. In addition, WebLogic allows you to build standard JAX-RPC clients that can discover and invoke web service operations dynamically. Finally, WebLogic provides client-side libraries that let you invoke web services over SSL and build J2ME clients over the CDC profile.

Because WebLogic's web services implementation conforms to all of the existing standards in the world of web services, clients we construct in this section are capable of interacting with any web service, not only those hosted by WebLogic. The JAX-RPC standard provides several ways to implement a web service client. For instance, the web service client can use a statically generated stub that acts as a proxy for the remote service. Such clients also are called static because the client is aware of the operations exposed by the web service. Alternatively, a client can invoke operations through a dynamic proxy generated at runtime using the WSDL for the web service. Such clients are called dynamic because the client needs to dynamically discover the operations exposed by the web service and then use this information to construct an "invocation."

19.5.1 Client-Side Libraries

WebLogic comes with several client libraries that package all the classes needed by Java clients to interact with web services:

webserviceclient.jar

This client-side JAR includes the runtime implementation of the JAX-RPC standard.

webserviceclient+ssl.jar

This client-side JAR includes all of the classes provided by webserviceclient.jar, and additional support for invoking web services protected by SSL.

webserviceclient+ssl_pj.jar

This client-side JAR includes the runtime implementation of JAX-RPC over SSL for the CDC profile of J2ME.

All of these client libraries can be found in the WL_HOMEserverlib directory of your WebLogic installation. If you are building a client application that invokes one or more web services, you can bundle the appropriate JAR along with your client.

If you intend to create dynamic clients, you need to choose only from one of the preceding client JARs. However, if you intend to build static clients, then you must use the clientgen Ant task to generate a client JAR specific to the web service that packages the stubs allowing clients to statically invoke operations exposed by the web service. Dynamic clients don't need this client JAR; they simply can rely on the JAX-RPC API to invoke both WebLogic and non-WebLogic web services. If you want to invoke a web service from within an EJB or a servlet hosted by WebLogic, you do not need to supply the webserviceclient.jar or any of its variations. However, if you want to make use of static clients, you need the JAR representing the client side stubs to be placed in the appropriate directory. For example, you could place the JAR in the WEB-INF/lib directory for a web application, or reference it from within the manifest file in an EJB.

19.5.2 Building a Client JAR for Static Clients

In order to create static clients, you must use the clientgen Ant task to generate a client JAR that web service clients can use to statically invoke the web service. The clientgen task can be invoked in two ways: either by referring to an existing EAR file that packages a WebLogic web service, or by pointing to the WSDL for an existing web service.

Example 19-5 showed how you can generate the client JAR from an existing EAR file that packages a WAR file representing a web service component:

ear="myPOJOEar.ear" warName="myWar.war" packageName="com.oreilly.wlguide.webservices.pojo.client" clientJar="../out/myPOJOClient.jar">

This clientgen task examines the specified EAR file (and the WAR file it packages) and creates a client JAR called myPOJOClient.jar, placing the generated classes into the oreilly.wlguide.webservices.pojo.client package. This deployable EAR file packages a WebLogic web service, possibly generated from a backend component using the servicegen task. The clientgen task also supports other features that we looked at earlier, including autotyping and the use of a type-mapping file.

Instead of pointing to an existing EAR file, you also could invoke the clientgen task by pointing to the WSDL file for an existing web service. The following portion from an Ant script shows how to generate an identical client JAR from an existing WSDL file:

wsdl="http://10.0.10.10:7001/pojoService/Simple?WSDL" packageName="com.oreilly.wlguide.webservices.pojo.client" clientJar="../out/myPOJOClientFromWSDL.jar">

In this case, the task creates a client JAR that allows clients to statically invoke operations on the web service described by the WSDL. Once again, the generated classes are placed in the oreilly.wlguide.webservices.pojo.client package. If the WSDL file describes multiple web services, you should use the serviceName attribute to identify the web service for which the client JAR should be generated. The same applies to a deployable EAR that packages multiple WebLogic web services.

You can also use the servicegen Ant task to generate the client JAR. In this case, the client JAR is packaged into the deployable EAR itself, and is made available as a download on the home page for the web service. If you've created the client JAR manually and would like to make it available on the web service home page, you must ensure that it is named correctly. If a web service named Foo is packaged within the EAR file, the corresponding client JAR within the EAR must be named Foo_client.jar. The following portion from a build script shows how to use the servicegen Ant task to also generate the client JAR:

 

In this case, the client JAR Simple_client.jar will be made available as a download on the home page for the web service. Because it will be packed within the generated WAR, it can be fetched using a URL such as http://10.0.10.10:7001/pojoService/Simple_client.jar. Java clients can then use this JAR to statically invoke any operation exposed by the Simple web service.

19.5.3 Static Clients

A client that statically invokes an operation relies on a strongly typed interface to interact with the web service. Once you've generated a client JAR specific to the web service, a static client can use the stubs generated specifically for that web service. These stubs serve as client-side proxies for the web service, exposing methods that let you directly invoke the operations of the web service.

As depicted in Figure 19-5, when a client invokes one of the methods on the stub, the call is forwarded via the JAX-RPC runtime to the server hosting the web service. At the server end, the JAX-RPC runtime decodes the request and invokes the appropriate web service operation. In WebLogic, these stubs are packaged within the client JAR generated specifically for the web service, while the server-side ties are generated automatically when the web service is deployed.

Figure 19-5. Static invocation

When writing a static client for a web service, you must include the web service-specific client JAR in your classpath. Example 19-6 illustrated how a client can statically invoke a web service operation. Let's take a closer look at the sequence of steps needed to interact with the web service.

First, the client configures WebLogic's implementation of the JAXM message factory and the JAX-RPC service factory:

System.setProperty("javax.xml.soap.MessageFactory", "weblogic.webservice.core.soap.MessageFactoryImpl"); System.setProperty("javax.xml.rpc.ServiceFactory", "weblogic.webservice.core.rpc.ServiceFactoryImpl");

These factory classes are available in the client JAR generated for the web service. When you use the clientgen task to generate the client JAR for a web service, WebLogic creates a Java interface that represents the web service and its operations. For our example, it is named SimplePort and it looks like this:

public interface SimplePort extends java.rmi.Remote { public String makeUpper(String arg) throws java.rmi.RemoteException; }

Notice how the SimplePort interface reflects the structure of the web service. In addition, WebLogic creates a Java interface that offers a convenient way of accessing the service port:

public interface com.oreilly.wlguide.webservices.pojo.client.Simple extends javax.xml.rpc.Service { public SimplePort getSimplePort( ) throws javax.xml.rpc.ServiceException; public SimplePort getSimplePort(String un, String pw) throws javax.xml.rpc.ServiceException; }

Finally, the client JAR packages the classes that implement both of these interfaces. The Simple_Impl class implements the Simple interface and ensures that when a client invokes the getSimplePort( ) method it actually acquires the stub implementation that also implements the SimplePort interface. Given this background, it is clear the client must use the SimpleImpl object to obtain the SimplePort stub implementation in order to interact with the web service:

Simple ws = new Simple_Impl("http://10.0.10.10:8001/pojoService/Simple?wsdl"); SimplePort port = ws.getSimplePort( );

In this case, we've created the javax.xml.rpc.Service instance from a URI that refers to the WSDL for the web service. Once you've obtained the SimplePort stub implementation, you can simply invoke the methods exposed by the client stub, which in turn ensures that the corresponding web service operation is invoked:

String returnVal = port.makeUpper("Hello There");

As we saw earlier, a client also can statically invoke web service operations that use out and in-out parameters. In that case, both the web service implementation and the client must use the corresponding holder classes for the out and in-out parameters.

19.5.4 Asynchronous Clients

WebLogic 8.1 lets clients invoke web service operations asynchronously. Instead of waiting for the operation to return a value, the client simply can continue and fetch the result of the operation later. In other words, WebLogic offers a non-blocking way of invoking an operation. This mechanism is ideal for long-running operations, provided you can architect your client application to retrieve the result at a later stage. This form of method invocation works regardless of whether the operation's invocation style is one-way (asynchronous) or request-response (synchronous).

To enable asynchronous invocations of web service operations, you need to generate client stubs that support this form of invocation for the web service. You can do this simply by adding a generateAsyncMethods attribute to the standard clientgen task:

generateAsyncMethods="true">

There are, in fact, three ways in which the client can behave after invoking an operation:

19.5.5 Dynamic Clients

Dynamic clients simply follow the JAX-RPC specification. Instead of using a statically generated stub to invoke an operation, these clients interact with the web service through a dynamic proxy, generated at runtime by examining the WSDL. The code in Example 19-12 shows how to use this dynamic approach to invoke the makeUpper operation exposed by our web service:

Example 19-12. A dynamic client

System.setProperty("javax.xml.soap.MessageFactory", "weblogic.webservice.core.soap.MessageFactoryImpl"); System.setProperty("javax.xml.rpc.ServiceFactory", "weblogic.webservice.core.rpc.ServiceFactoryImpl"); // Create the service factory ServiceFactory factory = ServiceFactory.newInstance( ); // Now specify the names for the service, port, and operation String targetNamespace = "http://www.oreilly.com/webservices/Simple"; QName serviceName = new QName(targetNamespace, "Simple"); QName portName = new QName(targetNamespace, "SimplePort"); QName operationName = new QName("makeUpper"); // Create the service URL wsdlLocation = new URL("http://10.0.10.10:8001/pojoService/Simple?WSDL"); Service service = factory.createService(wsdlLocation, serviceName); // Create the call Call call = service.createCall(portName, operationName); // Invoke the operation, passing in a parameter String result = (String) call.invoke(new Object[] {"lowercase string"});

This example illustrates the typical sequence of steps needed to invoke any web service. After initializing the JAXM and JAX-RPC factories, you create a ServiceFactory instance, which lets you construct a Service instance that represents the available web service. The Service object allows you to interact with the web service and create a Call object for invoking a web service operation. Remember, you do not need to use the clientgen task to generate the web service-specific client JAR because this client JAR is needed only by static clients. All of these standard JAX-RPC classes are located within the javax.xml.rpc package and are available within the webserviceclient.jar client library that includes WebLogic's JAX-RPC implementation. If your client makes use of custom datatypes, you still need to provide serialization classes to the client, which you can create conveniently using the autotype task.

If a dynamic client doesn't use the WSDL to create a Service instance, it must explicitly supply the information that is usually gleaned from the WSDL. This includes the parameters expected by the operation, the return value, the target endpoint address, and so on. The following example again shows how to invoke the makeUpper operation, this time without using the WSDL that describes the web service:

// Specify the names for the service, port, and operation as above // Create the service Service service = factory.createService(serviceName); // Create the call Call call = service.createCall(portName, operationName); // Now we need to specify the parameters and the return type call.addParameter("string", new QName("http://www.w3.org/2001/XMLSchema", "string"), ParameterMode.IN); call.setReturnType(new QName("http://www.w3.org/2001/XMLSchema", "string")); // Specify the endpoint of the web service call.setTargetEndpointAddress("http://10.0.10.10:8001/Simple"); // Now invoke the operation in the same way String result = (String) call.invoke(new Object[] { "lowercase string" }); System.out.println("Result of call is:" + result);

Clearly, you can write such a client only if you have all the information needed to invoke the web service operation, such as the name and type of the parameters. In this case, we've obtained this information by inspecting the WSDL that describes the web service.

There are certain caveats you should be aware of when building dynamic clients for a web service:

19.5.5.1 Introspecting the WSDL

As Example 19-12 illustrates, when you create a dynamic client, you must manually construct the parameters and return values for the calls, based on the WSDL for the web service that is being invoked.

WebLogic provides a weblogic.webservice.extensions.WLCall interface that can be used to conveniently introspect the WSDL of the web service. It extends the standard javax.xml.rpc.Call interface with three additional methods:

These methods ease the task of creating dynamic clients against an unknown WSDL. To illustrate the interface, we will simply continue with Example 19-12:

WLCall wlCall = (WLCall) call; for (Iterator i = wlCall.getParameterNames( ); i.hasNext( );) { String parm = (String) i.next( ); System.out.println("Parameter name is: " + parm); System.out.println("Type is: " + wlCall.getParameterJavaType(parm)); System.out.println("Mode is: " + wlCall.getParameterMode(parm)); }

This yields the following output:

Parameter name is: string Type is: class java.lang.String Mode is: IN

Note that the parameter names enable you to access the type and mode information, and reflect the names used in the web-services.xml descriptor (not the parameter names used in the implementation of the web service operation).

19.5.5.2 Registering datatype mapping information

When clientgen creates a client JAR for a static client, it ensures that any custom and WebLogic datatypes and their serialization classes are registered with the standard type-mapping machinery. Clients then can seamlessly use custom datatypes.

For dynamic web service clients however, the custom datatypes will not be registered automatically. Instead, you must use the JAX-RPC API to register custom datatypes manually. Using this API is cumbersome, as the dynamic client must register a number of datatypes, including some internal WebLogic serialization classes. For example, given a type implemented by a class MyType and its codec MyTypeCodec, a dynamic client must register the type as follows:

TypeMappingRegistry registry = service.getTypeMappingRegistry( ); TypeMapping mapping = registry.getTypeMapping( SOAPConstants.URI_NS_SOAP_ENCODING ); mapping.register( SOAPStruct.class, new QName( "http://typeurl.org/xsd", "MyType" ), new MyTypeCodec( ), new MyTypeCodec( ) );

A dynamic client can then use the custom type. For example, it can register the custom type as the return type for a web service call:

call.setReturnType(new QName( "http://typeurl.org/xsd", "MyType" ) );

Recall how the autotype task generates a type-mapping file types.xml, together with the serialization and deserialization classes and custom datatype. WebLogic provides a utility class, weblogic.webservice.encoding.DefaultTypeMapping, which can conveniently use this type-mapping file to register all of the listed custom datatypes in one call. Here is an example:

TypeMappingRegistry registry = service.getTypeMappingRegistry( ); registry.registerDefault(new DefaultTypeMapping("types.xml"));

You also can use the GenericTypeMapping class to associate all custom XML datatypes to the generic datatype SOAPElement, though this is usually less useful than the previous approach.

19.5.6 SOAP 1.2

By default, both WebLogic 7.0 and WebLogic 8.1 use SOAP 1.1, although both can accept SOAP 1.1- and SOAP 1.2-compliant message requests from clients. WebLogic 8.1 can be configured to produce SOAP 1.2 compliant messages too. To do this, modify your servicegen task as follows:

useSOAP12="true">

This task generates a web-services.xml that includes an additional useSOAP12 attribute:

useSOAP12="true" name="Simple" style="rpc" uri="/Simple" targetNamespace="http://www.oreilly.com/webservices/Simple" >

When you create the client JAR using this web definition, WebLogic generates two ports in the Service implementation:

The following code sample shows how a client can interact with the web service using SOAP 1.2 exchanges:

Simple ws = new Simple_Impl(where); SimplePort port = ws.getSimplePortSoap12( ); System.out.println("The service returned: " + port.makeUpper("hello there"));

19.5.7 J2ME Clients

You also can use the clientgen Ant task to generate a web service-specific client JAR for J2ME clients running on the CDC and Foundation profiles. In order to generate a client JAR for J2ME clients, you simply need to add a j2me="True" attribute to the clientgen Ant task.

j2me="True" />

Unlike the client JAR generated for non-J2ME clients, the web service-specific stub does not implement the java.rmi.Remote interface, and thus its methods do not throw a java.rmi.RemoteException either.

If you intend to build dynamic J2ME clients that don't need to talk over SSL, you simply can use the webserviceclient.jar library. However, if the J2ME client needs to interact with a web service that is using SSL, the webserviceclient+ssl_pj.jar library must be included in your classpath instead. Moreover, if the client uses the WSDL to invoke a web service, you must package a local copy of the WSDL for the client. A J2ME client cannot access a remote WSDL using a URLConnection object. Besides these issues, writing web service clients for J2ME devices is identical to writing non-J2ME clients.

19.5.8 Client Portability

Suppose you've created a web service client that runs on WebLogic and relies on client JARs either the standard client libraries provided by WebLogic or the web service-specific client JARs generated by the clientgen Ant task. In such a case, you may observe that the Java classes in the client JAR collide with the JAX-RPC implementation classes in WebLogic itself. This problem becomes even more apparent when the client JAR is deployed to a WebLogic release that is different from the one from which it was generated. In other words, the client JARs used to support the web service client actually conflict with WebLogic's JAX-RPC implementation classes.

To get around this problem you need to run the client through a conversion program that renames any accessed packages to a version-specific package name that doesn't collide with any of the server-side classes. For instance, in a WebLogic 8.1 environment, the tool renames all weblogic.* packages to weblogic81.*. You then can include the wsclient81.jar[1] library in your classpath instead of the webserviceclient.jar library. This client JAR contains the same files as the webserviceclient.jar library, but under the modified package name. Both client JARs are located under the WL_HOME/server/lib folder. To execute this change, you must run the setenv script to set up the environment and then invoke the conversion tool as follows:

[1] Include the wsclient70.jar for the equivalent functionality in WebLogic 7.0.

java weblogic.webservice.tools.versioning.VersionMaker output_directory yourClient.jar yourSupportingLibs.jar

This will modify the client and any supported library JARs, and place the modified version in the output directory. Remember, this conversion tool merely avoids any potential conflicts for web service clients running on WebLogic itself. For standalone web service clients, this change is unnecessary.

19.5.9 Client System Properties

Table 19-4 lists the system properties that Java clients can utilize when interacting with a web service. The weblogic.webservice.verbose property also can be specified on the server side and is particularly useful during debugging. It may cause some performance degradation, so you should enable it only during debugging.

Table 19-4. System properties useful for web service clients of WebLogic 8.1

System property

Description

weblogic.webservice.transport.http.

full-url

Set this property to true if you want the full URL of the web service, rather than its relative URL, to be specified in the Request-URI field of the HTTP request.

weblogic.webservice.transport.https.

proxy.host

and

weblogic.webservice.transport.https. proxy.port

If your client makes HTTPS connections that have to go through a proxy, use these properties to specify the hostname and port of the proxy server.

weblogic.webservice.verbose

Set this property to true to cause the SOAP request and response messages to be written to the console.

weblogic.webservice.client.ssl. strictcertchecking

This property, which defaults to false, enables or disables strict certificate validation when using WebLogic's SSL implementation.

weblogic.webservice.client.ssl. trustedcertfile

Set this property to the name of the file that contains the certificates of the CA, which allows you to trust the certificate issued by WebLogic Server. It also can contain certificates that you trust directly.

weblogic.webservice.client.ssl. adapterclass

If the client uses a third-part SSL implementation to interact with an SSL-protected web service, set this property to the fully qualified name of the SSL adapter class.

WebLogic 8.1 runs on the JDK 1.4, which provides a number of additional properties that you may want to use in your clients. For example, you can use http.proxyHost and http.proxyPort to specify the proxy server characteristics, http.keepAlive to determine whether persistent connections may be used, sun.net.client.defaultConnectTimeout to specify the millisecond timeout used to establish a connection, and sun.net.client.defaultReadTimeout to specify the timeout during a read.

Table 19-5 lists the system properties that Java clients can utilize when interacting with a web service. These are in addition to the properties listed in Table 19-4.

Table 19-5. System properties useful for web service clients of WebLogic 7.0

System property

Description

weblogic.webservice.transport.http.proxy.host

and

weblogic.webservice.transport.http.proxy.port

If your client makes HTTP connections that have to go through a proxy, use these properties to specify the hostname and port of the proxy server.

weblogic.http.

KeepAliveTimeoutSeconds

This determines the number of seconds to maintain HTTP keepalive before timing out the request. It defaults to 30 seconds. Set this to to disable HTTP keepalive.

Категории