Moving Past the Basics


In the last section, we worked through examples demonstrating how to write a basic Web Service and different types of Web Service clients . Now, let s start to look at more advanced features of WebLogic Server that you can use to address more complex requirements. We do not attempt to provide exhaustive coverage of these features, but rather try to make you aware of their existence. For more information, please refer to the WebLogic Server Web Services documentation at http://edocs.bea.com/wls/docs81/ webservices .html .

Using Document-Style Messaging

JAX-RPC 1.0 requires that WebLogic Server provides support for doc/literal (document-style with literal use) message formats. When building a Web Service endpoint or client starting from WSDL, the WebLogic Server-provided Ant tasks will automatically determine the format being used from the WSDL binding definitions. When building a Web Service starting with Java you need a way to specify that the Web Service will support the doc/literal message format. WebLogic Server determines the appropriate style for the Web Service by looking at the web-service element s style attribute in the web-services .xml deployment descriptor (see http://edocs.bea.com/wls/docs81/webserv/wsp.html for more information). Fortunately, you can use the style attribute of the service element in the servicegen Ant task to accomplish this automatically, as shown here:

 <servicegen destEar="MyWebService.ear" contextURI="/web-services">   <service javaClassComponents="MyWebServiceImpl"            targetNamespace="http://www.xxx.com/"            serviceName="MyWebService" serviceURI="/MyWebService"  style="document"  /> </servicegen> 

When using document-style Web Services, WebLogic Server requires that the Java component s method to which the Web Service call is mapped have only a single argument. Typically, you would use a Java type for this single argument that is capable of representing any XML document, like javax.xml.soap.SOAPElement or org.w3c.dom.Document . Of course, nothing prevents you from mapping to a strongly typed Java class either.

Customizing a Web Service Home Page

As we saw in the last section, WebLogic Server automatically generates a home page for your Web Service. This page is dynamically generated and therefore cannot be modified. While this is sufficient for many applications, you may want to provide your own custom home page for Web Services that are used by customers or business partners . To do this, simply add the appropriate files to the Web Service s Web application and designate the appropriate welcome file in the web.xml deployment descriptor, as shown here:

 <!DOCTYPE web-app PUBLIC -//Sun Microsystems, Inc.//DTD Web Application 2.3//EN                          http://java.sun.com/dtd/web-app_2_3.dtd> <web-app>     <welcome-file-list>         <welcome-file>PropertySearchService.html</welcome-file>     </welcome-file-list> </web-app> 

Adding a custom home page to the Web Service does not disable the default page. To disable the default page, you must set the web-service element s exposeWSDL attribute in the web-services.xml deployment descriptor to false . Be aware that this disables not only the default home page but also access to the dynamically generated WSDL and to the HTTP-based testing facilities that the default home page uses. This seems reasonable because we expect that most Web services made available for external consumption will not want to allow any of these default features to be exposed.

Best Practice  

When building a custom home page for your Web Service, set exposeWSDL to false in the web-services.xml deployment descriptor to disable the default home page. Be aware that if you want users to be able to access the WSDL for your Web Service, you will need to publish the WSDL statically.

Publishing Static WSDL

By default, WebLogic Server dynamically generates WSDL for your Web Service based on the contents of the web-services.xml deployment descriptor. In some cases, you might want to publish your own static WSDL to provide additional information to users of your Web Service. Publishing your own static WSDL is almost as easy as adding your own custom home page. First, generate your WSDL and save it to a file; the WebLogic Server-provided wsdlgen Ant task is a convenient way of accomplishing this. Then, add the appropriate mime-mapping entry to your web.xml deployment descriptor:

 <mime-mapping>     <extension>wsdl</extension>     <mime-type>text/xml</mime-type> </mime-mapping> 

As discussed earlier in the chapter, publishing your Web Service s WSDL statically does not automatically disable access to the dynamically generated WSDL. To prevent access to the dynamic WSDL, set exposeWSDL to false in the web-services.xml deployment descriptor.

Warning  

If you choose to publish a Web Service s WSDL statically, it is your responsibility to keep it up to date with the Web Service if any changes are made.

The examples contains a working Web Service that uses a custom home page and publishes its WSDL statically. You can download the examples for this chapter from the companion Web site (http://www. wiley .com/compbooks/masteringweblogic).

Using Web Service Sessions

Web Services operations are stateless. When building enterprise applications, it is often necessary to maintain state between client invocations. While Web Services deployed in WebLogic Server have at their disposal all of the power of the J2EE container for persisting state to a back-end database or other EIS, there are situations where you might prefer a lighter-weight mechanism to maintain state across Web Service operations. WebLogic Server gives your Web Services a mechanism for accessing and using the HttpSession , including the ability to do session persistence.

To use Web Service sessions, your Web Service implementation class simply obtains the WebServiceSession from the current WebServiceContext and uses it as a Web application would use an HttpSession , as shown in Listing 15.4.

Listing 15.4:  CountingServiceImpl.java.
start example
 package mastering.weblogic.ch15.example5; import java.util.ArrayList; import weblogic.webservice.context.ContextNotFoundException; import weblogic.webservice.context.WebServiceContext; import weblogic.webservice.context.WebServiceSession; public class CountingServiceImpl {     public int count()     {         WebServiceSession session;         try {             session = WebServiceContext.currentContext().getSession();         }         catch (ContextNotFoundException cnfe) {             cnfe.printStackTrace();             return -1;         }         Integer count = (Integer)session.getAttribute("count");         if (count == null)             count = new Integer(0);         count = new Integer(count.intValue() + 1);         session.setAttribute("count", count);         return count.intValue();     } } 
end example
 

In the client, you simply need to make sure that your client reads the JSESSIONID value from the response and attaches it to the next request. Fortunately, the WebLogic Server client run time does this automatically behind the scenes for you. The client simply reuses the same stub to submit subsequent requests , and the server is able to retrieve the WebServiceSession associated with that client session. Look at the CountingClient class in the examples to see how this works.

Using Custom Serializers

A JAX-RPC run time needs a mechanism to convert Web Service data between Java and XML for nonbuilt-in data types. While the JAX-RPC 1.0 specification provides an example serialization framework that is used in the reference implementation, it does not specify the interfaces that a framework must support for interoperability. Therefore, you should realize that any time you write custom serializers that plug into a JAX-RPC run-time serialization framework, you are writing vendor-specific code. We expect that future versions of the JAX-RPC specification will mandate that any JAX-RPC-compliant serialization framework must support a set of standard APIs and have well-defined behaviors.

WebLogic Server uses a serialization framework to handle the conversion of Web Service data between Java and XML. Use of any nonbuilt-in data type as an argument or return value of a Web Service operation requires a custom serializer class. As we have seen, WebLogic Server can automatically generate these serializer classes for most custom data types. In the event that you need to write your own serializer, WebLogic Server gives you the capability to do so. In this section, we look at the mechanics of writing a custom serializer that converts between a SOAP array of strings and a Java ArrayList ”something that is not supported by the built-in auto-typing mechanism.

In the custom serializer example, we start with the very simple Web Service implementation class shown in Listing 15.5.

Listing 15.5:  NumbersToStringsServiceImpl.java.
start example
 package mastering.weblogic.ch15.example6; import java.util.ArrayList; public class NumbersToStringsServiceImpl {     public ArrayList getResults(int numberOfStrings)     {         if (numberOfStrings < 0)             return new ArrayList(0);         ArrayList results = new ArrayList(numberOfStrings);         for (int i = 0; i < numberOfStrings; i++)             results.add(Integer.toString(i + 1));         return results;     } } 
end example
 

Next, we need to write the serializer class. WebLogic Server provides an abstract class that your serializer class will need to extend: weblogic.webservice.encoding.AbstractCodec . The WebLogic Server JAX-RPC serialization framework uses the WebLogic XML Streaming API for manipulating the XML. If you are not familiar with this API and want more information, please refer to the BEA Web site at http://edocs.bea.com/wls/docs81/xml/xml_stream.html. Your serializer class needs to implement the following three methods :

 public void serialize(Object obj, XMLName name, XMLOutputStream writer,                       SerializationContext context) throws SerializationException; public Object deserialize(XMLName name, XMLInputStream reader,                           DeserializationContext context) throws DeserializationException; public Object deserialize(XMLName name, Attribute attribute,                           DeserializationContext context)     throws DeserializationException; 

WebLogic Server will invoke the serialize() method when it needs to convert a Java object into XML. The first deserialize() method is used when it needs to convert the XML on the input stream to an appropriate Java object. The second deserialize() method is important only when your data type is used as an attribute value in the XML. In the interest of space, we have chosen not to list the source code for the ArrayListOfStringsCodec serializer class. Please download the examples from the companion Web site before proceeding.

Let s take a look at the serialize() method. The first element we need to write has the attribute that defines the type and size of the array. This attribute refers to three different namespaces that should already be defined in the higher-level SOAP envelope; just in case, we create namespace attributes for each that match our namespace references in the attribute name and value. As a result, we need to create a Start_Element using the element name passed in by WebLogic Server, the type attribute that identifies this element as an array of strings of the appropriate length, and the three namespace attributes:

 ... ArrayList attrList = new ArrayList(1); Attribute attr =     ElementFactory.createAttribute("soapenc:arrayType",                                     ("xsd:string[" + length + "]")); attrList.add(attr); Iterator attrIter = attrList.iterator(); AttributeIterator attrs =     ElementFactory.createAttributeIterator(attrIter); ArrayList nsList = new ArrayList(3); Attribute soapenc =     ElementFactory.createNamespaceAttribute("soapenc",                                             SOAPENC_NAMESPACE_URI); nsList.add(soapenc); Attribute xsd =     ElementFactory.createNamespaceAttribute("xsd",                                             XSD_NAMESPACE_URI); nsList.add(xsd); Attribute xsi =     ElementFactory.createNamespaceAttribute("xsi",                                             XSI_NAMESPACE_URI); nsList.add(xsi); Iterator nsIter = nsList.iterator(); AttributeIterator nsAttrs =          ElementFactory.createAttributeIterator(nsIter); try {     StartElement start =          ElementFactory.createStartElement(name, attrs, nsAttrs);     writer.add(start); 

Next, each element we write will have a name of xsd:string and type attribute of xsi:type=xsd:string :

 XMLName xsdStringName = ElementFactory.createXMLName(xsd:string);     attr = ElementFactory.createAttribute(xsi:type, xsd:string);     attrList.clear();     attrList.add(attr);             attrIter = attrList.iterator();     attrs = ElementFactory.createAttributeIterator(attrIter); 

Finally, we loop through each element in the ArrayList and create an entry for each value in the list and then end the document:

 for (int i = 0; i < length; i++) {         String value = (String)list.get(i);         writer.add(ElementFactory.createStartElement(xsdStringName,                                                      attrs));         writer.add(ElementFactory.createCharacterData(value));         writer.add(ElementFactory.createEndElement(xsdStringName));     }     writer.add(ElementFactory.createEndElement(name)); } catch (XMLStreamException xse) {     throw new SerializationException(Stream error, xse); } 

The deserialize() method is even simpler. First, we get the soapenc:arrayType attribute from the enclosing element so that we can parse the value to determine the number of elements present in the array. We could have simply parsed the XML to determine this, but that would have made our example a little longer and not quite as easy to understand. Once we know the length, we find each entry s start element, get its enclosing contents, and add it to the ArrayList . Notice that we were careful to finish reading the entire XML element including the final end element. This is very important so that you leave the XMLInputStream in a consistent state for any other deserializers that may be invoked after yours.

Warning  

When deserializing XML data in a custom serializer class, you must read the entire element off the stream before returning the Java object. Failure to do so can cause other deserialization that occurs after yours to fail because of improper cursor positioning.

Now, we are ready to assemble our Web Service. In this case, we need to create the web-services.xml by hand because we want to define the XML data type that corresponds to an array of strings and map that to our custom serializer. Rather than write the whole thing by hand, we cheated by changing the type signature in Numbers -ToStringsServiceImpl.java to use String[] instead of ArrayList and used the source2wsdd Ant task to generate a deployment descriptor that we could modify. Doing this, our deployment descriptor types section is equivalent to the XML shown here:

 <types>   <xsd:schema xmlns:xsd=http://www.w3.org/2001/XMLSchema               xmlns:stns=java:language_builtins.lang               elementFormDefault=qualified               attributeFormDefault=qualified               targetNamespace=java:language_builtins.lang>     <xsd:import namespace=http://schemas.xmlsoap.org/soap/encoding//>     <xsd:complexType name=ArrayOfString>       <xsd:complexContent>         <xsd:restriction base=soapenc:Array             xmlns:soapenc=http://schemas.xmlsoap.org/soap/encoding/>           <xsd:attribute xmlns:wsdl=http://schemas.xmlsoap.org/wsdl/               ref=soapenc:arrayType wsdl:arrayType=xsd:string[]>           </xsd:attribute>         </xsd:restriction>       </xsd:complexContent>     </xsd:complexType>   </xsd:schema> </types> 

After hand-editing the type-mapping -entry to register our custom serializer, the type-mapping section looks like the one shown here:

 <type-mapping>   <type-mapping-entry xmlns:p1=java:language_builtins.lang       deserializer=           mastering.weblogic.ch15.example6.ArrayListOfStringsCodec       type=p1:ArrayOfString       serializer=           mastering.weblogic.ch15.example6.ArrayListOfStringsCodec       class-name=java.util.ArrayList>   </type-mapping-entry> </type-mapping> 

This allows us to use an ArrayList in our server-side implementation class but be flexible in our client. If the client chooses to use the WebLogic Server JAX-RPC run time and our custom serializer, it can receive the array of strings that the service returns in an ArrayList . There is nothing preventing it from generating its client directly from the WSDL and receiving the string list using the standard data structure on its particular Web Services run time.

Once we have the web-services.xml , we need to create a web.xml deployment descriptor for the Web Service s Web application and the application.xml for the Web Service s enterprise application. From there, we use Ant s built-in war and ear tasks to package everything into a deployable enterprise application. If we want, we can use clientgen to generate a client jar file. If we use clientgen with the useServerTypes attribute set to true, the generated stubs will use the ArrayList . Setting it to false causes the generated stubs to use String[] .

As this simple example demonstrates , custom serializers are one way to decrease the coupling between your WSDL and your Web Service implementation.

Using SOAP Handlers

Sometimes, you need to get access to the raw SOAP message either before it is sent or before the data is converted to Java. For example, you might need to compress messages before they are sent and decompress them before the JAX-RPC run time converts the XML data to Java objects. WebLogic Server s JAX-RPC run time supports the use of SOAP handlers on the client as well as the server. In this section, we will write a simple handler to write the SOAP request and response to a log file. This code can be found in the SOAP handler example on the companion Web site.

Handlers should extend the abstract class, javax.xml.rpc.handlers.Generic-Handler , and implement only the functions required to provide the necessary functionality. The three primary methods of interest are handleRequest() , handleResponse() , and handleFault() . When a request is received by the JAX-RPC run time, the run time invokes the handleRequest() method of each handler in the handler chain in order. When the response is returned, each handler s handle-Response() method is invoked in reverse order. If the response is a SOAPFault , the handleFault() method call replaces the call to handleResponse() .

A handler can short-circuit the handler chain by returning false . If a handler returns false from a handleRequest() method, the rest of the handler chain and the back-end component is short-circuited, and the response handler chain starts with the handleResponse() or handleFault() method of the handler that short-circuited the call. If the handleResponse() or handleFault() method returns false , the rest of the handler chain is skipped and the current response message is returned to the client. For more information about handlers, please see the JAX-RPC specification at http://java.sun.com/xml/ jaxrpc / .

Now, let s look at the important parts of our LoggingHandler class, which can be found in SOAP handler example on the companion Web site. The first thing to notice is the location of all of the XML- related classes we are importing:

 import javax.xml.namespace.QName; import javax.xml.rpc.JAXRPCException; import javax.xml.rpc.handler.GenericHandler; import javax.xml.rpc.handler.HandlerInfo; import javax.xml.rpc.handler.MessageContext; import javax.xml.rpc.handler.soap.SOAPMessageContext; import javax.xml.soap.Name; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPMessage; import javax.xml.soap.Text; 

The javax.xml.rpc package contains the core JAX-RPC classes while the javax.xml.soap package contains the core classes needed to create and manipulate SOAP messages from Java, as defined by the SAAJ 1.1 specification.

In our LoggingHandler , we override the default implementations of the init() and destroy() methods defined in the GenericHandler class to trap the handler s life-cycle events. We use these to open and close the log file when the handler is created and destroyed , respectively:

 public void init(HandlerInfo config) {     this.config = config;     Map hConf = config.getHandlerConfig();     String logDirectoryName =         (String)hConf.get(LOG_DIRECTORY_ATTR_NAME);     String logFilePrefix =         (String)hConf.get(LOG_FILE_PREFIX_ATTR_NAME);     if (logFilePrefix == null  logDirectoryName == null)          throw new RuntimeException(handler not configured);     try {         File logDirectory = new File(logDirectoryName);         if (!logDirectory.exists())             throw new RuntimeException(directory does not exist:  +                                        logDirectoryName);         File logFile =             File.createTempFile(logFilePrefix, .log, logDirectory);         log = new PrintWriter(new FileOutputStream(logFile));     }     catch (IOException ioe) {         ioe.printStackTrace();         throw new RuntimeException(IOException:  +                                    ioe.getMessage());     } } public void destroy() {     log.close(); } 

The getHeaders() method is supposed to return the list of headers that this handler will process. Because we are simply writing out the entire SOAP envelope to the log file, we use a simple implementation that returns the list from the configuration information. getHeaders() is the only method in the GenericHandler class that does not have a default implementation.

 public QName[] getHeaders() {     return config.getHeaders(); } 

Finally, we implement the handleRequest() , handleResponse() , and handle-Fault() methods that do the real work. The handleRequest() method implementation is shown here:

 public boolean handleRequest(MessageContext mc) {     try {         SOAPMessageContext ctx = (SOAPMessageContext) mc;         SOAPMessage request = ctx.getMessage();         SOAPElement envelope = request.getSOAPPart().getEnvelope();         writeRequestLog(envelope);     }     catch(SOAPException e) {         e.printStackTrace();         throw new JAXRPCException(e);     }     return true; } 

In the handleRequest() method, we have access to the entire context of the SOAP message. In our example, we simply get the SOAPMessage from the MessageContext and use it to retrieve the SOAPEnvelope . Once we have the SOAPEnvelope , the writeRequestLog() method walks through the SOAPEnvelope and prints out all of the XML information contained in the message. Because responses and faults have a similar structure to a SOAP request, handleResponse() and handleFault() are virtually identical to handleRequest() .

Now that we have our handler, we need to understand how to use it. Handlers can be used on both the client and server JAX-RPC run times. To use a handler on the server, you need to declare a handler chain in the web-services.xml deployment descriptor and associate the handler chain with the appropriate operations. For handlers requiring no initialization parameters, you can use the handlerChain child element of the servicegen Ant task to declare the handlers and associate them with every operation in the Web Service. If you need to specify initialization parameters or want to use the handler chain selectively with only certain operations, then it will be necessary to edit the web-services.xml file directly. Take a look at the deployment descriptor for the LoggingHandler example to see exactly how this is accomplished.

Clients can also use handlers. To use a handler from the WebLogic Server JAX-RPC client run time, you will need to register your handler programmatically. The following code fragment shows the basics of how to register your handler:

 QName portName = new QName("http://www.bigrez.com/ch15/example7",                            "NumbersToStringsServicePort"); HandlerRegistry registry = service.getHandlerRegistry(); List handlerChain = registry.getHandlerChain(portName); Map handlerConfig = new HashMap(); handlerConfig.put("logFilePrefix", LOG_FILE_PREFIX); handlerConfig.put("logFileDirectory", LOG_FILE_DIRECTORY); HandlerInfo handler =     new HandlerInfo(LoggingHandler.class, handlerConfig, null); handlerChain.add(handler); registry.setHandlerChain(portName, handlerChain); 

Using SOAP Attachments

SOAP attachments provide a way to attach any type of data to the SOAP message. Currently, this is the primary mechanism you would typically use to pass non-XML data along with the SOAP message. In the future, there may be other ways of including non-XML data in line in the SOAP message, but for now, SOAP attachments are the standard way to accomplish this. SAAJ provides a set of classes used to represent and manipulate SOAP attachments. Let s look at an example of how to use SOAP attachments.

In this example, we have written a simple file transfer Web Service that accepts the name of a server-side file and attaches the contents of the file to the SOAP response using a SOAP handler and the SAAJ APIs. The back-end service implementation doesn t do anything beyond making sure that the file being requested is valid and setting the MIME type of the file, as shown in Listing 15.6. For our simple example, we are using an XML document, so determining the correct MIME type is easy.

Listing 15.6:  FileTransferServiceImpl.java.
start example
 package mastering.weblogic.ch15.example8; import java.io.FileNotFoundException; import java.net.URL; import javax.xml.namespace.QName; import javax.xml.rpc.holders.StringHolder; import javax.xml.rpc.soap.SOAPFaultException; import weblogic.webservice.util.FaultUtil; public class FileTransferServiceImpl {     public void getFile(StringHolder fileName, StringHolder mimeType)         throws SOAPFaultException     {         String name = fileName.value;         URL file = this.getClass().getClassLoader().getResource(name);         if (file == null) {             throw new SOAPFaultException(new QName("http://schemas.xmlsoap.org/soap/envelope/",                           "Server"),                 "Unknown File", "file server",                 FaultUtil.newDetail(new FileNotFoundException("file not found: " + name)));         }         int index = name.lastIndexOf(".");         if (index == -1) {             throw new SOAPFaultException(new QName("http://schemas.xmlsoap.org/soap/envelope/",                           "Server"),                 "Unknown File Type", "file server",                 FaultUtil.newDetail(new FileNotFoundException("invalid file name: " +                                               name)));         }         String extension = name.substring(index + 1, name.length());         if (extension.equalsIgnoreCase("xml")) {             mimeType.value = "text/xml";         else if (extension.equalsIgnoreCase("html")) {             mimeType.value = "text/html";         else if (extension.equalsIgnoreCase("txt")) {             mimeType.value = "text/plain";         }         else {             throw new SOAPFaultException(new QName("http://schemas.xmlsoap.org/soap/envelope/",                           "Server"),                 "Unknown File Type", "file server",                 FaultUtil.newDetail(new FileNotFoundException("invalid file type: " +                                               extension)));         }     } } 
end example
 

This example introduces two new concepts. First, this Web Service example uses method parameters as a way to pass information back to the client (and, more importantly, the client- and server-side handlers). fileName is an in-out parameter used by the client to pass the name of the file to retrieve from the server. The primary reason for doing this is to make it easy to tell the handler the name of the file to attach to the response. In addition, mimeType is an out parameter that we use to tell the handler the MIME type of the file to attach. We could have just as easily put this logic in the server-side handler; however, using an out parameter allows us to pass the MIME type information back to the client (or client-side handler). Ultimately, we chose to use the out parameter to illustrate how to program a Web Service using both in-out and out parameters.

The second new concept is the explicit use of the SOAPFaultException as a mechanism to return error information to the caller. WebLogic Server automatically maps exceptions thrown in the server to SOAP faults as best it can. The richness of information included in SOAP faults, though, is generally greater than what the server can determine from an ordinary exception. Using the SOAPFaultException provides an explicit way to control the contents of the SOAP fault message sent back to the caller. For more details about the contents of SOAP faults and what the parameters mean, please see the SOAP specification at http://www.w3.org/TR/SOAP/ and the Javadocs for javax.xml.rpc.soap.SOAPFaultException .

Best Practice  

Web Service implementations should use SOAPFaultException to pass back the context of the error explicitly to the Web Service container, ensuring that the content of the SOAP fault response includes detailed information regarding the nature of the problem.

Let s look at the server-side handler that is attaching the contents of the file to the SOAP response. Rather than listing the entire contents of the ServerFileTransferHandler.java file, let s concentrate on those parts of the code that are specific to the attachment. As the following code fragment taken from ServerFileTransfer-Handler shows, creating the attachment and attaching it to the message is pretty simple once we have parsed the SOAP response to get the file s name and MIME type and read the contents of the file into a byte array:

 AttachmentPart attachment = response.createAttachmentPart(); if (text/xml.equals(mimeType)) {     attachment.setContent(new String(fileBytes), mimeType); } else if (text/html.equals(mimeType)) {     attachment.setContent(new String(fileBytes), mimeType); } else if (text/plain.equals(mimeType)) {     attachment.setContent(new String(fileBytes), mimeType); } else {     attachment.setContent(fileBytes, mimeType); } response.addAttachmentPart(attachment); 

Because WebLogic Server expects the contents of text/xml attachments to be of type String , we do a simple test to see if the contents are XML, HTML, or plain text. If they are not, we try to set the content using the byte array directly. This obviously doesn t work for every case and is used to illustrate the point that the JAX-RPC run time may expect certain content formatting for objects of a particular MIME type. This expectation is controlled by the particular set of DataContentHandlers objects in use.

On the client, our example uses the ClientFileTransferHandler class to write the attachment s contents to the file system. Again, the DataContentHandlers determine the type of object returned by the AttachmentPart.getContent() method. SAAJ requires that the object returned be either a typed Java object that represents the type or an InputStream that can be used to read the raw bytes. The following code excerpt from ClientFileTransferHandler.java shows how we retrieve the attachment in order to read it:

 iter = response.getAttachments(); AttachmentPart attachment = null; if (iter.hasNext()) {     attachment = (AttachmentPart)iter.next(); } else {     throw new SOAPException(Malformed message:  +                             attachment not found); } InputStream obj = (InputStream)attachment.getContent(); int len = attachment.getSize(); 

One thing we did not do was change the fileName return value. In our client handler, we are writing the file to a preconfigured directory on the file system that is passed into the handler during initialization. To enhance the handler s functionality, we could have written our handler so that it changes the response fileName to include the directory information defining where the handler has written the file on the client s file system. This would be a good exercise for you to get some hands-on experience with the SAAJ APIs to see what you can do with handlers.

Using JMS as the Transport

When using Web Services with WebLogic Server, SOAP messages are sent using the HTTP (or HTTPS) protocol; however, WebLogic Server also supports sending SOAP messages using JMS as the transport layer. Using JMS as the transport can be more efficient for processes that exchange large numbers of messages because the underlying RMI infrastructure used by JMS maintains a persistent socket connection between the sender and the receiver.

To build a Web Service that supports the JMS protocol (as well as HTTP), you simply need to set the web-service element s jmsUri attribute in the web-services.xml deployment descriptor. The format of the jmsUri attribute is < JMS ConnectionFactory JNDI Name > / < JMS Queue JNDI Name >. In the next example, we created a JMS connection factory with a JNDI name of Ch15_example9_ConnectionFactory and a queue with a JNDI name of Ch15_example9_Queue . The following code fragment from the web-services.xml deployment descriptor shows the web-services element; the highlighted section illustrates the change needed to support the JMS transport:

 <web-service useSOAP12="false"              targetNamespace="http://www.bigrez.com/ch15/example9/"              name="NumbersToStringsService" style="rpc"              uri="/NumbersToStringsService"  jmsUri="Ch15_example9_ConnectionFactory/Ch15_example9_Queue"  > 

We need to create the necessary JMS objects before trying to deploy our Web service. In this example, you can use the create_jms target in the Chapter 15 examples build.xml file to create a JMS server, a queue that belongs to the JMS server, a JMS connection factory, and a JMS template (make sure your server is running first). The JMS template is required because the Web Services JMS transport uses temporary destinations to return the responses. WebLogic JMS requires a temporary template for any JMS server that supports temporary destinations. Therefore, the script sets the Temporary Template attribute of our JMS server to point at the TemporaryTemplate template it created.

Once we have our client-specific jar file, we can write the client. The client looks almost exactly like any other client except that there is an additional port class that uses JMS as the transport, as shown here in the code fragment from the Numbers-ToStringsClient.java file:

 NumbersToStringsService service =     new NumbersToStringsService_Impl(WSDL_LOCATION); NumbersToStringsServicePort port =     service.getNumbersToStringsServicePort(); String[] result = port.getResults(numberOfStrings); System.out.println(HTTP: getResults(+ numberOfStrings +) returned  + result.length +  strings.); for (int i = 0; i < result.length; i++)     System.out.println(\tString  + (i + 1) +  is:  + result[i]); NumbersToStringsServicePort jmsPort =     service.getNumbersToStringsServicePortJMS(); String[] jmsResult = jmsPort.getResults(numberOfStrings); System.out.println(JMS: getResults(+ numberOfStrings +) returned  + jmsResult.length +  strings.); for (int i = 0; i < jmsResult.length; i++) System.out.println(\tString  + (i + 1) +  is:  + jmsResult[i]); weblogic.webservice.binding.jms.ConnectionPool.getInstance().close(); 

To run the client, we need to use the WebLogic Server client run time. This can be accomplished using either the weblogic.jar or the new thin client that leverages the JDK s support for IIOP. By using the weblogic.jar , we use the traditional WebLogic RMI implementation, which defaults to the t3 protocol. To use the new thin client, we need to include wlclient.jar , which provides the basic RMI support, and the wljmsclient.jar, which provides the extra support needed for JMS. When using the new thin client with JMS as the transport, we need to add an extra call to tell the JAX-RPC client infrastructure to close the JMS connection; otherwise , the client program won t exit cleanly. This occurs because of a known issue in the Java IDL specification that requires the client to un-export all classes before the JVM is allowed to shut down. If you don t like the idea of using this method, you can simply replace the return call at the end of your main method with System.exit(0) . We expect that BEA will work closely with Sun in an attempt to resolve this issue and remove the need to call this close() method explicitly in the future.

Using Web Services Security

Web Services Security is an emerging standard for securing Web Services. At the time of writing, work on the draft specification is being done by the Organization for the Advancement of Structured Information Standards (OASIS, see www.oasis-open.org/ committees /wss/ for more information). WS Security is essentially focused on providing end-to-end message-level security (as opposed to just transport-level security, such as that offered by SSL). This translates into three different areas of focus:

  • Message confidentiality through the use of encryption

  • Message integrity through the use of digital signatures

  • Identity propagation through the ability to pass security tokens as part of the message

At the time of writing, WebLogic Server 8.1 provides an implementation that follows an early draft of the WS Security specification. We expect that BEA will support the final specification once it becomes official. Until such a time, there is no guarantee that any vendor s Web Services security implementation will interoperate with any other vendor s implementation. For more up-to-date information, we suggest looking at the recommendation coming out of the Web Service Interoperability Organization (WS-I); see www.ws-i.org/ for more information.

WebLogic Server provides support for end-to-end Web Services security by providing transport- and message-level security as well as access control. In this section, we will first show you how to achieve transport-level security through the use of both one-way and two-way SSL. Then, we will show you how to use message-level security that provides for encrypting and/or digitally signing the SOAP message itself. We end this chapter by showing you how to protect your Web Services from unauthorized access. As in Chapter 10, we strongly recommend the use of domestic-strength certificates for transport- or message-level security. At the time of writing, you still have to contact BEA to obtain a domestic-strength license key.

Transport-Level Security

WebLogic Server provides transport-level security through the use of one-way or two-way SSL connections between the client and the server. We have already covered how to set up and configure a WebLogic Server for SSL in Chapter 10. We also looked at how to use SSL from a Java application client using either the RMI programming model or programmatic HTTP via the URLConnection object. This section is simply an extension of the Java application client discussion for Web Services clients using the WebLogic Server JAX-RPC client run time. If you need more information, please refer back to Chapter 10.

Setting up one-way SSL with the server is easy. As with other Java application clients, it is possible to use either the WebLogic Server s SSL or a third-party SSL implementation. Unfortunately, using a third-party implementation such as JSSE currently requires creating your own SSLAdapter , a task that we do not feel is reasonable and therefore will not cover. For more information on using third-party SSL implementations, see the WebLogic Server documentation at http://edocs.bea.com/wls/docs81/webserv/security.html .

To use the WebLogic Server SSL implementation, all you need to do is specify the certificates to trust, set a few additional command-line arguments, and use the webserviceclient+ssl.jar client run-time jar file in place of webserviceclient.jar . A complete working example is located on the companion Web site.

The Chapter 15 example build script defines the create_keystores task to automate the creation of the key stores, but you still need to configure the server to use the ServerKeyStore.jks and ServerTrustStore.jks key stores that the script creates in the ${SERVER_ROOT}/${DOMAIN} directory, as defined by the build.properties file.

At the time of writing, the only way to set the trusted certificates is to add a few lines of code to the client:

 import weblogic.webservice.client.BaseWLSSLAdapter; import weblogic.webservice.client.SSLAdapterFactory; ... SSLAdapterFactory factory = SSLAdapterFactory.getDefaultFactory(); BaseWLSSLAdapter adapter =      (BaseWLSSLAdapter)factory.getDefaultAdapter(); adapter.setTrustedCertificatesFile(CertGenCA.pem); 

Notice that the setTrustedCertificatesFile() method requires the use of a PEM-encoded file rather than a trust key store. We expect that this shortcoming may be addressed by the time you read this, so please check the WebLogic Server documentation at http://edocs.bea.com/wls/docs81/webserv/security.html for more up-to-date information.

You also need to set the Java system property bea.home to tell the WebLogic Server client run time where to find the BEA license file, which is currently required for using BEA s SSL implementation:

 java -Dbea.home=c:\bea ... 

To make your client use two-way SSL, add code to retrieve the client s certificate chain and private key and set them using the BaseWLSSLAdapter.addIdentity() method:

 import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import weblogic.webservice.client.BaseWLSSLAdapter; import weblogic.webservice.client.SSLAdapterFactory; ... KeyStore identityKeyStore = KeyStore.getInstance(jks); identityKeyStore.load(new FileInputStream(clientKeyStoreFileName),                       clientKeyStorePassword); PrivateKey privateKey = (PrivateKey)     identityKeyStore.getKey(privateKeyAlias,                             privateKeyPassphrase.toCharArray()); Certificate [] certChain =     identityKeyStore.getCertificateChain(args[3]); X509Certificate[] x509CertChain =      new X509Certificate[certChain.length]; for (int i = 0; i < certChain.length; i++)     x509CertChain[i] = (X509Certificate)certChain[i]; SSLAdapterFactory factory = SSLAdapterFactory.getDefaultFactory(); BaseWLSSLAdapter adapter =      (BaseWLSSLAdapter)factory.getDefaultAdapter(); adapter.setTrustedCertificatesFile(CertGenCA.pem); adapter.addIdentity(x509CertChain, privateKey); ... 

The only trick is to convert the certificate chain returned from the call to getCertificateChain() to the X509Certificate[] type that the addIdentity() method requires. At the time of writing, the version of the addIdentity() method we are using is mistakenly marked as deprecated. We expect this to be fixed by the time you read this book.

Don t forget to change the server s Two Way Client Cert Behavior attribute to either Client Certs Requested But Not Enforced or Client Certs Requested And Enforced to enable two-way SSL. We are not enforcing client certificates in the example to allow both the one-way and two-way SSL clients to run against the same server.

Message-Level Security

Through the use of digital certificates, a client can encrypt the SOAP message, digitally sign it, or do both. With that said, let s look at another example to see how to configure and use message-level security with WebLogic Server 8.1.

First, you need to create the client and server certificates and import the certificates, private keys, and trusted CA certificates into key stores that the client and server will use. We do not repeat the description of this process here because it is described in the Setting Up SSL/TLS section of Chapter 10.

If you are using digital signatures, configure the Identity Asserter so that WebLogic Server can validate the certificate used for signing the message. For our example, we use the built-in capabilities of the DefaultIdentityAsserter and Default User Name Mapper to map the CN attribute of the X.509 certificate that contains the machine s hostname to the user because the certificates created by utils .CertGen do not have a username or email address associated with them. Therefore, the first thing that you need to do is create a user with the same name as the hostname of your machine. After creating the appropriate user, you need to add support for X509 token types to DefaultIdentityAsserter and configure the Default User Name Mapper to extract the CN field of the certificate, as described in the Identity Assertion section of Chapter 10.

Once this configuration work is finished, we are ready to configure the Web service. The message-level security settings in the web-services.xm l file are fairly complex, so we suggest that you start with servicegen to generate the initial deployment descriptor even if you ultimately have to edit the deployment descriptor by hand. Simply set the signKeyName and/or encryptKeyName attributes to the alias you used to import the server s private key into the key store and the signKeyPass and/or encryptKeyPass attributes to the server s private key passphrase for the features you want to use:

 <servicegen classpathref="dev.class.path" destEar="ch15_example9.ear"             contextURI="/ch15_example9">   <service javaClassComponents="${pkg_base}9.NumbersToStringsServiceImpl"            targetNamespace="http://www.bigrez.com/ch15/example9/"            serviceName="NumbersToStringsService"            serviceURI="/NumbersToStringsService">     <client useServerTypes="true" packageName="${pkg_base}9"/>   <  security signKeyName="server_key" signKeyPass="server_key_passwd"   encryptKeyName="server_key"   encryptKeyPass="server_key_passwd"/  >   </service> </servicegen> 

Optionally, use the username and password attributes to have the server attach an identity token to the SOAP responses.

If you look at the relevant section of a Web Service deployment descriptor created by servicegen , you will see that the private key passphrases and user password are all in clear text. WebLogic Server provides the weblogic.webservice.encryptpass utility that allows you to encrypt these passwords. Note that this encryptpass utility encrypts the passwords for decoding by a specific domain. This means that you will need to rerun the utility against the plain-text version for the domain to which you are deploying the Web Service.

Next, you need to make changes to the client code invoking the secure Web Service. Much of the client code is standard Java 2 security code, so we will not go through that here; see Chapter 10 for more information. If you look at the compile_example11 task, you will see that we are generating the NumbersToStringsClient.java file from a template where we replace the username and password being used by the client. Let s look at the code fragments taken from NumbersToStringsClient.java shown here:

 ...  import java.security.PrivateKey;   import java.security.cert.X509Certificate;   import weblogic.webservice.context.WebServiceContext;   import weblogic.webservice.context.WebServiceSession;   import weblogic.webservice.core.handler.WSSEClientHandler;  ...  X509Certificate clientcert =   getCertificate(clientKeyAlias, clientKeyStoreFileName,   clientKeyStorePassword);   PrivateKey clientpk =   getPrivateKey(clientKeyAlias, privateKeyPassphrase,   clientKeyStoreFileName, clientKeyStorePassword);  NumbersToStringsService service =     new NumbersToStringsService_Impl(WSDL_LOCATION);  WebServiceContext context = service.context();   WebServiceSession session = context.getSession();   session.setAttribute(WSSEClientHandler.CERT_ATTRIBUTE, clientcert);   session.setAttribute(WSSEClientHandler.KEY_ATTRIBUTE, clientpk);  NumbersToStringsServicePort port =     service.getNumbersToStringsServicePort(); String[] result = port.getResults(numberOfStrings); 

The bolded sections illustrate new elements that are specific to WebLogic Server Web Service security. First, we import some Java 2 security classes and a few WebLogic Server-specific classes. Next, we retrieve the client certificate and private keys from the client s key store. Finally, we get the WebServiceSession object and set the security-related attributes before getting the port and invoking the operation.

Finally, to run the example we need to add the wsse.jar file to the client s classpath. If you look at the run_example11 task, you will notice that we are setting another Java system property called weblogic.webservice.verbose to true . When you run the client with this property, the client will dump both the SOAP request and SOAP response to standard out. This feature is very useful when trying to debug Web Service requests.

Tip  

When debugging a Web Service, set the Java system property weblogic.webservice.verbose to true to make the client print the SOAP messages to standard out.

Access Control

WebLogic Server allows you to restrict access to your Web Service using the same mechanisms that you would employ for any other J2EE component. For example, restricting access to your EJB component that implements your Web Service prevents unauthorized uses from invoking it. For Web Services whose back-end components are regular Java objects, you must control access to the Web Service s URL using the standard Web application security mechanisms discussed in Chapters 4 and 10.

When using protected Web Service resources, the client needs to authenticate to WebLogic Server before accessing the Web Service. You do this either through simple username/password authentication or by using two-way SSL as an authentication mechanism; see the Setting Up SSL/TLS section of Chapter 10 for more information. To authenticate your Web Service client, set the javax.xml.rpc.security.auth.username and javax.xml.rpc.security.auth.password properties on the service endpoint stub:

 port.setProperty(javax.xml.rpc.security.auth.username, rpatrick); port.setProperty(javax.xml.rpc.security.auth.password, password); 

clientgen also creates a specialized version of the getXXXPort() method that accepts a username and password as arguments to eliminate the need for setting the properties shown previously:

 NumbersToStringsServiceImplPort port =     service.getNumbersToStringsServiceImplPort(rpatrick, password); 

Two way SSL with Identity Assertion is another way for the user to get access to a protected Web Service.

If you are protecting the Web Service URL directly, your client must have access to the WSDL. There are two ways to accomplish this. The first method is to use the no-args version of the service s constructor. This causes the client run time to use the WSDL packaged with the client jar file created by clientgen :

 NumbersToStringsServiceImpl_Impl service =     new NumbersToStringsServiceImpl_Impl(); 

The catch with this option is that currently this WSDL always uses http://localhost:7001/ < contextURI > / < serviceURI > as the location of the Web service. You may need to edit the location URLs in the WSDL in order for this method to work for real applications. The second method is to remap the WSDL location to something other than the default. Doing this requires you to publish static WSDL. See the downloadable code at http://www.wiley.com/compbooks/masteringweblogic for a working example that demonstrates the features described in this section.

The servicegen task allows you to require the use of HTTPS for all clients of a particular Web Service through its protocol attribute on the service element. Using this simply sets the transport-guarantee element in the web.xml to CONFIDENTIAL.




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

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