(877)-718-2617


Using Web Services in Java

One of the design goals of the web services specification was interoperability, so it was designed to be neutral to a platform, language or framework. Java is not an exception, and there are several options for working with web services in Java projects. You can choose from many frameworks, tools and vendors, and we will not force you to any particular solution, but for this quick start guide we adopted XFire web services framework, because in our opinion it is simple, intuitive and developer friendly. Regardless of framework of your choice the procedure should be the same:

  1. Configure code generation so that web services stubs and data transfer objects are generated for you from WSDL files by the machine and you don't need to code it by hand.
  2. Configure security settings in web services client following WS-Security standard so that TargetProcess can authenticate client requests.
  3. Make proper use of the generated API to call TargetProcess remotely and implement use cases of your interest.

In this mini guide we will describe these steps in details using our preferred tools and frameworks. Let's begin with prerequisites:

There is no reason to use older JDK releases, unless you support a legacy project, so we recommend using JDK 5 or earlier. It got quite stable and mature with lots of useful enhancements in language we will make use of. As we stated earlier, we prefer XFire framework for its simplicity and efficiency. However you probably should take a look at CeltiXFire framework which is successor of XFire.

Maven is an excellent tool for build management with lot of functionality available in plugins, we will make use of. You will also need TargetProcess WSDL files, which you can download to your local file system, or you can refer them online if your tools support it. XFire supports both options. You can browse WSDL files at http://[your_tp_path]/Services/. To download a particular WSDL file, point your browser to http:// [your_tp_path]/Services/[service_name].asmx?WSDL, for instance bugs web service on local host can be found at http://localhost/tp/Services/BugService.asmx?WSDL.

Configuring Java Project

Setting Up Project Dependencies

You will need to add XFire libraries to your project. Let's do it by example, add the following fragment in your POM file:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  ...
  <dependencies>
    <dependency>
      <groupId>org.codehaus.xfire</groupId>
      <artifactId>xfire-java5</artifactId>
      <version>${xfire.version}</version>
    </dependency>

    <dependency>
      <groupId>org.codehaus.xfire</groupId>
      <artifactId>xfire-annotations</artifactId>
      <version>${xfire.version}</version>
    </dependency>

    <dependency>
      <groupId>org.codehaus.xfire</groupId>
      <artifactId>xfire-jaxb2</artifactId>
      <version>${xfire.version}</version>
    </dependency>

    <dependency>
      <groupId>org.codehaus.xfire</groupId>
      <artifactId>xfire-ws-security</artifactId>
      <version>${xfire.version}</version>
    </dependency>
    ...

As you see, we included xfire-java5, xfire-annotiations, xfire-jaxb2 and xfire-ws-security dependencies to the project. These dependencies also include additional, transitive dependencies, such as xfire-core, jaxb api, jaxb impl, jaxb xjc, wsdl4j, wss4j and others. We specified XFire version in property xfire.version, whose definition you will find later.

Enabling Code Generation

Now we need to configure code generation from WSDL files. XFire provides Ant task WsGenTask for these purposes, which we need to configure by means of AntRun Maven plugin. In the following example AntRun plugin executes WsGenTask task twice to generate code from WSDL files ProjectService.wsdl and ReleaseService.wsdl:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  ...
  <build>
  ...
    <plugins>
      ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <executions>
          <execution>
            <phase>generate-sources</phase>
            <configuration>
              <tasks>
                <delete dir="${xfire.src.dir}"/>
                <taskdef name="wsgen"
                         classname="org.codehaus.xfire.gen.WsGenTask"
                         classpathref="maven.compile.classpath"/>
                <wsgen outputDirectory="${xfire.src.dir}"
                       wsdl="${basedir}/src/main/wsdl/ProjectService.wsdl"
                       package="com.targetprocess.integration.project"
                       externalBindings="${basedir}/src/main/jaxb/bindings-project.xml"
                       generateServerStubs="false"
                       overwrite="true"/>
                <wsgen outputDirectory="${xfire.src.dir}"
                       wsdl="${basedir}/src/main/wsdl/ReleaseService.wsdl"
                       package="com.targetprocess.integration.release"
                       externalBindings="${basedir}/src/main/jaxb/bindings-release.xml"
                       generateServerStubs="false"
                       overwrite="true"/>

                ... add more WSDL files here ...

              </tasks>
              <sourceRoot>${xfire.src.dir}</sourceRoot>
            </configuration>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
        </executions>
        <dependencies>
          <dependency>
            <groupId>org.codehaus.xfire</groupId>
            <artifactId>xfire-generator</artifactId>
            <version>${xfire.version}</version>
          </dependency>
        </dependencies>
      </plugin>

You may add more WSDL files to the generation, but be aware that it is better to generate classes into different packages, otherwise conflicts can happen. You must configure these properties if the WsGenTask task for successful generation:

Name

Description

outputDirectory

Specifies directory where the generated sources will be placed.

wsdl

Specifies where WSDL file is located. This can be local file system path or URL of an online WSDL resource.

package

Specifies package name for the generated classes. Please use different packages for different WSDL files, otherwise conflicts can happen.

externalBindings

Specifies path to JAXB XSD-to-Java compiler configuration file. In this file you can customize code generation options, such us provide specific package name, common super type, etc. There must be one compiler configuration file for every WSDL file.

To eliminate configuration duplicates, we extracted common settings to the global properties of POM file:

  <properties>
    <xfire.version>1.2.6</xfire.version>
    <xfire.src.dir>${basedir}/target/generated-sources/xfire</xfire.src.dir>
  </properties>

There may be newer XFire version when you read this documentation. You may add more properties, such as TargetProcess URL to find online WSDL files, etc.

Customizing Code Generation

In this example we use JAXB 2 bindings to serialize Java classes as XML messages. JAXB provides compiler, which given an XML schema (XSD) file will generate a set of Java classes representing types of this XML schema. This is what WsGenTask task described above does: given a WSDL file it extracts XML schema portion from it and conveys it to the JAXB compiler, then JAXB compiler generates Java classes used as data-transfer objects.

By default JAXB compiler chooses package name specified in the WSDL file, which is com.targetprocess in case of TargetProcess web services. However this causes conflicts when sources from multiple WSDL files are generated in the same package. To avoid conflicts compiler needs to be customized using an external bindings configuration file. Path to this file should be specified in the externalBindings property of the WsGenTask task. Here is an example content of a bindings customization file:

<jxb:bindings version="1.0"
              xmlns:xs="http://www.w3.org/2001/XMLSchema"
              xmlns:jxb="http://java.sun.com/xml/ns/jaxb">
  <jxb:bindings schemaLocation="../wsdl/ProjectService.wsdl#types?schema1" node="/xs:schema">
    <jxb:schemaBindings>
      <jxb:package name="com.targetprocess.integration.project"/>
      ...
    </jxb:schemaBindings>
    ...
  </jxb:bindings>
</jxb:bindings>

In this example we customized package name for the generated classes. You can customize other options.

For more information on customizing JAXB bindings see Sun Java Web Services tutorial.

Using the Generated API

XFire makes using web services straightforward, however some initial preparation is required. TargetProcess follows WS-Security specification for authenticating user requests, so clients have to provide proper credentials in order for TargetProcess to validate incoming messages and authenticate clients.

Configuring Authentication

XFire integrates with WSS4J which is a Java library implementing the WS Security standard. It can be used to provide user credentials, sign and verify, encrypt and decrypt SOAP messages. See how to enable WS Security support in XFire. Here we provide a brief excerpt from this page with only relevant information.

To enable WS-Security support you must add two handlers to the client's outgoing handler chain: org.codehaus.xfire.util.dom.DOMOutHandler - Converts from StAX to DOM format for WS Security. org.codehaus.xfire.security.wss4j.WSS4JOutHandler - Performs the WS Security related functions. Here is code fragment which demonstrates programmatic XFire client configuration example:

// Classes from this example are imported from the following packages.
import org.codehaus.xfire.client.Client;
import org.codehaus.xfire.util.dom.DOMOutHandler;
import org.codehaus.xfire.security.wss4j.WSS4JOutHandler;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.apache.ws.security.WSConstants;

...

// URL of the TargetProcess bugs web service.
String url = "http://localhost/tp/Services/BugService.asmx";

// Instantiate web service stub.
BugServiceClient bugServiceClient = new BugServiceClient();
BugServiceSoap bugService = bugServiceClient.getBugServiceSoap(url);

// Begin configuring web service client.
Client client = Client.getInstance(bugService);

// Add first outgoing handler which converts from StAX to DOM format for WS-Security.
client.addOutHandler(new DOMOutHandler());

// Configure second outgoing handler which performs WS-Security related activities.
Properties properties = new Properties();
// We will transmit user name and password to the web service,
properties.setProperty(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
// Here we specify actual user name.
properties.setProperty(WSHandlerConstants.USER, "admin");
// We will transmit password as is, without hashing it.
properties.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
// Specify name of the handler class which will be invoken when remote web service is called.
// This handler will update outgoing SOAP message with WS-Security header,
// specifying user name and password.
properties.setProperty(WSHandlerConstants.PW_CALLBACK_CLASS, PasswordHandler.class.getName());

// Add second outgoing handler which performs WS-Security related activities.
client.addOutHandler(new WSS4JOutHandler(properties));

...

// Call web service!
BugDTO bug = bugService.getByID(1);

Here is an example of PasswordHandler class used in the code fragment above:

package com.targetprocess.integration;

import org.apache.ws.security.WSPasswordCallback;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class PasswordHandler implements CallbackHandler {

    /** 
     * Simple database which maps user names to passwords. 
     * It is a good idea to externalize this data.
     */
    private final Map passwords = new HashMap();

    public PasswordHandler() {
        this.passwords.put("admin", "admin"); // Add user name/password pair.

    }

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        WSPasswordCallback callback = (WSPasswordCallback) callbacks[0];
        callback.setPassword(this.passwords.get(callback.getIdentifer()));
    }

}

You can troubleshoot authentication issues by monitoring HTTP traffic between your client and TargetProcess web services. Every SOAP message must contain WS Security headers, as in the following example:

<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
 <soap:Header>
  <wsse:Security soap:mustUnderstand="1"
   xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
   <wsse:UsernameToken 
    wsu:Id="UsernameToken-6722010"
    xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <wsse:Username 
     xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
     user name
    </wsse:Username>
    <wsse:Password
     xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
     Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">
     password
    </wsse:Password>
   </wsse:UsernameToken>
  </wsse:Security>
 </soap:Header>
 <soap:Body>
  <ns1:Create xmlns:ns1="http://targetprocess.com">
   <ns1:entity>
    <ns1:ID xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
    <ns1:ProjectID xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
    <ns1:Name>...</ns1:Name>
    ... payload goes here ...
   </ns1:entity>
  </ns1:Create>
 </soap:Body>
</soap:Envelope>