Over a decade after the emergence of the first implementations, the clouds of hype have fully dissipated, and Web Services are proving to be viable, relevant, and effective for the development of business-to-business and business-to-consumer applications all over the world.
With the SOA (Service Oriented Architecture) movement, enterprise level data-flow and communications architectures are now being built on top of the continually maturing web services foundation. Benefiting from a decade of the development community’s focus and attention, web services have become increasingly robust, interoperable, and easy to use. This easy to use aspect is poised to become the enabling element that ushers in a whole new generation of web services advocates and enthusiasts.
If you haven’t revisited web services since the initial hype, it’s time to take another look. Web services are here to stay.
This article provides an exploration of Java web services appropriate for 2007. It shows how new generation open source web services frameworks, such as XFire, combined with rich support from development environments, can make the development of non-trivial web services truly simple.
You will get hands-on guidance to building a web service in Java using XFire, then a Java client in the Eclipse 3.2 IDE. The finale is the creation of a .NET 2.0-based client for the web service, built within a few minutes, using Visual Studio 2005.
Building on top of HTTP, Simple Object Access Protocol (SOAP) provides the ability to:
While SOAP on top of HTTP provides the mechanism for automated transactions and information flow over the web, interconnect code still needs to be written to take advantage of this ability. The difficulty in writing such code has been steadily declining over the past decade. Today, you are able to write web service clients in almost any programming language. By and large, however, most web services are written either in Java or C#.
Tools are available today to generate most of the glue code that is necessary to make web services work. The enabling technology in this area is the World Wide Web Consortium (W3C) standardization of Web Services Description Language (WSDL). See www.w3.org/TR/wsdl for information on the latest WSDL technical report.
A WSDL file fully describes the API available through a web service. With the assistance of an XML schema, the data types of return values and arguments can be fully specified. There is enough information in a WSDL, together with its associated schemas, to construct client code in any supporting languages for the web service. To make a web service available, then, all you need is to expose a copy of the service’s WSDL to your potential clients. Automated discovery of web services is specified as Universal Description, Discovery, and Integration (UDDI). However, even without discovery capability, the WSDL information can be directly exposed via a specific URL. The example that we are coding in this article exposes the WSDL to the client via a specific URL.
In this example, we’ll be using a Java web services framework called XFire to expose Plain Old Java Objects (POJOs) as web service. Figure 1 shows the big picture.
In Figure 1, the XFire engine exposes our Java objects as a web service and generates a WSDL dynamically for us. The entire web service bundle is deployed as a web application on Tomcat 5.5.20 (any later version should also work). This web service exposes a method to retrieve information on orders placed through an ecommerce store. The Order object that is retrieved is a non-trivial Java object. In fact, the Order object contains references to other custom Java objects, including a collection of LineItems objects and a reference to a Customer object. Figure 2 shows the composition of an Order object.
We’ll create client code to access this web service in both the Java environment and, using C#, the .NET environment. In both cases, most of the client code is created automatically by the IDE. In the case of Java, you will use Eclipse 3.2’s web service client generation support, based on Apache AXIS. In the case of .NET, you will use Visual Studio 2005’s support for web references via .NET 2.0.
During the binding process, the annotated Java data type that you define in your source code is transformed into XML schema defined types; the annotated methods of your Java objects become web service methods.
In Figure 3 binding for POJO is performed using Aegis, the binding layer of XFire. Aegis also supports other bindings including Castor, XMLBeans, as well as JAXB 1.1 (JSR-33) and JAXB 2.0 (JSR–222). You may wish to use Castor binding if you want direct mapping to relational database capabilities. XMLBeans provides expressive coverage when you are working from XML schemas and you want to preserve the XML schema during binding; while JAXB (Java Architecture for XML Binding) bindings are applicable if you working with standards that are part of Java EE.
Based on your annotated POJOs, the XFire engine can:
The Aegis POJO binding supports only Code first development at this time; but you can use the JAXB 2.0 binding if you need to do Schema first development. The example in this article will illustrate Code First development with Aegis; see XFire documentation for details on using Schema first development using the JAXB 2.0 binding.
Table 1: Alternative transports | |
Transport | Description |
JMS (Java Message Service) | Store and forward message queue based transport |
In-JVM | Bypasses the networking IO and can provide high performance communications |
XMPP (Extensible Message and Persistence Protocol) | Formerly Jabber protocol – see www.xmpp.org |
Any JBI (Java Business Integration) transports | Channel based integration with Apache ServiceMix (see my VSJ December 2005 article Plug into JBI with ServiceMix, also see incubator.apache.org/servicemix) |
As an example, if you repurpose your web service software components for local single process deployment you may embed XFire in your application and then use the In-JVM transport to communicate between client and service components.
You will find the code for the example under the code directory of the distribution. The first thing to notice is the typical Maven 2 directory layout for the project.
Figure 5 shows the directory layout – the pom.xml is the project object model descriptor file required by Maven 2 (similar function to Ant’s build.xml if you’re more familiar with Ant). The target directory is created by Maven 2 when you compile and create the web service WAR file for deployment.
You will find the source tree rooted under the main directory. The central object class of this web service is the uk.co.vsj.xfire.shop.Order class.
An Order class contains an order number of String type, an array of LineItem type, and a reference to a customer (of Customer type). You will rely on XFire to generate the proper XML data binding and to marshal objects of this complex type across the web service.
The code for Order is shown here with its constructor and getter/setter accessor methods. You may want to reference Figure 2 to see the composition of this class. Note that there is no customer annotation in this source file:
package uk.co.vsj.xfire.shop;public class Order {String orderNumber;LineItem [] lineItems;Customer customer;// constructorspublic Order(String orderNumber,LineItem[] lineItems,Customer customer) {this.orderNumber = orderNumber;this.lineItems = lineItems;this.customer = customer;}// getters and setterspublic Customer getCustomer() {return customer;}public void setCustomer(Customer customer) {this.customer = customer;}public LineItem[] getLineItems() {return lineItems;}public void setLineItems(LineItem[] lineItems) {this.lineItems = lineItems;}public String getOrderNumber() {return orderNumber;}public void setOrderNumber(String orderNumber) {this.orderNumber = orderNumber;}}The Customer class contains name and phone information, both of String type, for the customer.
The code for Customer is shown in the following listing; note again that there is no special annotation:
package uk.co.vsj.xfire.shop;public class Customer {private String custName;private String phone;public Customer(String custName,String phone) {this.custName = custName;this.phone = phone;}public String getCustName() {return custName;}public void setCustName(String custName) {this.custName = custName;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}}The last component of the Order class is the LineItem class. Each LineItem contains an inventory identification SKU, a description, and a price. The following listing shows the LineItem class:
package uk.co.vsj.xfire.shop;public class LineItem {private int quantity;private String sku;private String description;private double price;public LineItem(int quantity,String sku,String description,double price) {this.quantity = quantity;this.sku = sku;this.description = description;this.price = price;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}public double getPrice() {return price;}public void setPrice(double price) {this.price = price;}public int getQuantity() {return quantity;}public void setQuantity(int quantity) {this.quantity = quantity;}public String getSku() {return sku;}public void setSku(String sku) {this.sku = sku;}}Up to this point in time, all the data objects are POJO classes and contain no special annotations or XFire-specific code additions. This enables them to be reused in any other application context. It also enables them to be unit tested easily.
The code for OrderQueryService is shown in the following listing, with annotations in red:
package uk.co.vsj.xfire.shop;import javax.jws.WebMethod;import javax.jws.WebParam;import javax.jws.WebResult;import javax.jws.WebService;@WebService(name = "OrderQuery",targetNamespace ="http://www.vsj.co.uk/2007/webservice/OrderQuery")public class OrderQueryService {private static StringTEST_ORDER_NUMBER = "ON111";@WebMethod(operationName ="retrieveOrder",action = "urn:RetrieveOrder")@WebResult(name = "orderRetrieved")public Order getOrder(@WebParam(name ="orderNumberParam")String ordno) {return findOrder(ordno);}private Order findOrder(String orderNumber) {Order order = null;if (orderNumber.equals(TEST_ORDER_NUMBER)) {// create oneLineItem [] lineItems =new LineItem[2];lineItems[0]= new LineItem(1,"TV12", "LCD TV", 299.99f);lineItems[1]= new LineItem(2,"LANC32","LAN Cable 30 feet",12.94f);Customer cust = new Customer("Joe LaRose", "232-2222");order = new Order(TEST_ORDER_NUMBER,lineItems, cust);}return order;}}The OrderQueryService above provides one single operation on the web service called retrieveOrder. This operation is mapped to the public getOrder() method of the class. The getOrder() method in turn delegates to a private findOrder() method to do the work. In this code, if the incoming order number is “ON111”, an Order instance is created and returned – simulating the actual retrieval of an existing order.
@WebService(name = "OrderQuery",targetNamespace ="http://www.vsj.co.uk/2007/webservice/OrderQuery")In the WSDL produced by XFire, this annotation generates the web service port type of QrderQuery, within the target namespace of http://www.vsj.co.uk/2007/webservice/OrderQuery.
This target namespace will typically be used by client code generation tools to create the name of the client code package.
The getOrder() method is annotated with:
@WebMethod(operationName ="retrieveOrder",action = "urn:RetrieveOrder")@WebResult(name = "orderRetrieved")In the WSDL, this annotation generates the web service operation retrieveOrder, together with the retrieveOrderRequest and retrieveOrderResponse definitions. The return value is specified via generated orderRetrieved elements in the WSDL.
The argument of the getOrder() method is annotated with:
@WebParam(name = "orderNumberParam")In the WSDL, this annotation will generate the appropriate input parameter for the web service operation.
The final WSDL generated by XFire is shown in the following edited listing (the full listing can be found at www.vsj.co.uk). You can see how the annotations cause the generation of the corresponding web service elements.
<?xml version="1.0" encoding="UTF-8"?><wsdl:definitionstargetNamespace="http://www.vsj.co.uk/2007/webservice/OrderQuery"xmlns:ns1="http://shop.xfire.vsj.co.uk"...><wsdl:types><xsd:schemaxmlns:xsd="http://www.w3.org/2001/XMLSchema"attributeFormDefault="qualified"elementFormDefault="qualified"targetNamespace="http://www.vsj.co.uk/2007/webservice/OrderQuery"><xsd:elementname="retrieveOrder"><xsd:complexType><xsd:sequence><xsd:elementmaxOccurs="1" minOccurs="1"name="orderNumberParam"nillable="true"type="xsd:string"/></xsd:sequence></xsd:complexType></xsd:element>... other elements removed</xsd:schema><xsd:schemaxmlns:xsd="http://www.w3.org/2001/XMLSchema"attributeFormDefault="qualified"elementFormDefault="qualified"targetNamespace="http://shop.xfire.vsj.co.uk"><xsd:complexType name="Order"><xsd:sequence><xsd:elementminOccurs="0"name="customer" nillable="true"type="ns1:Customer"/><xsd:element minOccurs="0"name="lineItems" nillable="true"type="ns1:ArrayOfLineItem"/><xsd:element minOccurs="0"name="orderNumber" nillable="true"type="xsd:string"/></xsd:sequence>Other data types message definitionsetc. removed for clarity</wsdl:definitions>This WSDL provides sufficient description for web service code generation tools to create client side access code for the OrderQueryService.
<beansxmlns="http://xfire.codehaus.org/config/1.0"><service><serviceClass>uk.co.vsj.xfire.shop.OrderQueryService</serviceClass><serviceFactory>jsr181</serviceFactory></service></beans>It tells the XFire engine that the class to process for the web service is uk.co.vsj.xfire.shop.OrderQueryService, and the processor (serviceFactory) to use is the JSR-181 annotation processor. Consult the XFire documentation for other service factories available (e.g. for JDK 1.4 backward compatibility).
Since the web service is deployed as a WAR file to Tomcat, the servlet must also be configured properly to execute the XFire engine. This is done via the standard deployment descriptor web.xml. web.xml is located in the webapps/WEB-INF directory.
The XFire engine for HTTP transport is executed via XFireConfigurableServlet, configured to be triggered by incoming URL /services/* (and /servlet/XFireServlet/*) URL patterns.
From within the code directory, where Maven 2’s configuration file pom.xml is located, issue the following command:
mvn packageThis will start Maven 2, compile your code, download the required dependencies, and create a deployable WAR file for your web service. The xfire-shop-1.0.war file is located in your target directory. It is a ready-to-deploy web service WAR.
Next, you need to deploy the xfire-shop-1.0.war file to Tomcat 5.5. The easiest way to deploy this application to Tomcat is:
http://localhost:8080/xfire-shop-1.0/services/OrderQueryService?WSDLThis URL causes the XFire engine to generate the OrderQueryServer WSDL. The content of the WSDL should be similar to the WSDL shown earlier. Figure 6 shows the WSDL displayed via the above URL in a browser.
This URL to the WSDL can be used by development tools and IDEs to generate the client code.
To generate a client for accessing OrderQueryService with Eclipse 3.2, take the following steps:
http://localhost:8080/xfire-shop-1.0/services/OrderQueryService?WSDL
package uk.co.vsj.xfire.client;import uk.co.vsj.www._2007.webservice.OrderQuery.OrderQuery;import uk.co.vsj.www._2007.webservice.OrderQuery.OrderQueryServiceLocator;import uk.co.vsj.xfire.shop.LineItem;import uk.co.vsj.xfire.shop.Order;public class WSClient {private static StringTEST_ORDER_ID = "ON111";public static void main(String[]args) throws Exception {OrderQueryServiceLocator loc =new OrderQueryServiceLocator();OrderQuery port = (OrderQuery)loc.getPort(OrderQuery.class);Order order =port.retrieveOrder(TEST_ORDER_ID);if (order != null ){System.out.println("ORDER NUMBER "+ TEST_ORDER_ID);System.out.println("");System.out.println("Name: "+ order.getCustomer().getCustName());System.out.println("Phone: " +order.getCustomer().getPhone());System.out.println("");String header =String.format("%-10s%-10s%-30s%-10s","Quantity", "SKU","Description", "Price");System.out.println(header);for ( LineItem li:order.getLineItems()) {String fmt =String.format("%-10d%-10s%-30s$%,8.2f",li.getQuantity(), li.getSku(),li.getDescription(), li.getPrice());System.out.println(fmt);}} else {System.out.println("Order not found");}}}You can find the code for WSClient.java in the Eclipse32 directory of the source distribution. This code uses the generated OrderQueryServiceLocator to obtain the web service’s port. It then invokes the retrieveOrder() operation (mapped to the getOrder() method by XFire) using “ON111” as an input parameter. The query result is then formatted and printed to System.out.
If you compile and run the WSClient code, it will connect to the OrderQueryService running under Tomcat, make the order query, and display the results as shown in Figure 7.
Note that the Apache Axis support runtime may complain about missing javax.activation.DataHandler and javax.mail.internet.MimeMultipart. You don’t need these for the example, but you may download JavaMail and JAF from Sun. These binaries cannot be freely included due to licensing restrictions. You will need to add mail.jar and activation.jar from the downloads to your Eclipse build classpath.
using System;using System.Text;using ConsoleApplication.localhost;namespace ConsoleApplication{class Program{static void Main(string[] args){OrderQueryService service =new OrderQueryService();Order order =service.retrieveOrder("ON111");Console.WriteLine("Order number: "+ order.orderNumber);Console.WriteLine("");Console.WriteLine("Name: " +order.customer.custName);Console.WriteLine("Phone: "+ order.customer.phone);Console.WriteLine("Items -");foreach (LineItem li inorder.lineItems){Console.WriteLine(li.quantity + " X " +li.description);}while (!Console.KeyAvailable){}}}}You can find the modified Program.cs file in the vs2005 directory of the source distribution. This C# code queries the web service for order number “ON111” and then prints out the details of the order, similar to the Eclipse example earlier. It also waits for a key to be pressed on the console before terminating – this allows you to see the output.
Build and run the application, you should see the Order information obtained by the C# .NET client, through the Tomcat hosted, XFire engine managed, POJO web service. The console window should look similar to Figure 8.
Many of these features are going into .NET 3.0, which includes the Windows Communications Framework (formerly codenamed Indigo). The good news this time around is that the traditional arch-rivals – Sun and Microsoft – have joined forces on Web Services technology (indeed, non-interoperable web services is about as useful as a proprietary communication protocol). Sun has also released reference implementation of WSI for Java – designed to interoperate with the WCF in the same areas.
聯(lián)系客服