How to implement a SOAP client using JAX-WS Liferay infrastructure

At the 2016 Liferay Symposium (Italy) I presented the topic: How to develop SOAP and REST services in JAX-WS and JAX-RS standard on Liferay. During the presentation I illustrated how to expose both REST (Representational State Transfer) and SOAP (Simple Object Access Protocol) services for each application using the Liferay Extender.

 

In this article I want to show you how to build a SOAP JAX-WS (Java API for XML Web Services) client using the Liferay infrastructure. Let's start with a concrete example. The entire development project for this article is available on my GitHub liferay-72-soap-client-examples repository.

 

1. Overview

Let's consider the SOAP Calculator service whose WSDL (Web Services Description Language) descriptor is available at the following address http://www.dneonline.com/calculator.asmx which exposes the four arithmetic operations between two integers.

 

The goal is to make this service available to any application installed on our Liferay instance. We then structure our Liferay project in this way:

 

  1. Implementation of the calculator-api module. This module defines the APIs that each application can invoke to use the services of arithmetic operations (addition, subtraction, division and multiplication);
  2. Implementation of the calculator-service module. This module implements the APIs defined by the calculator-api module and acts as a client to the SOAP Calculator service;
  3. Implementation of the calculator-gogo-shell module. This module implements the Gogo Shell commands that use the APIs defined by the calculator-api module to perform the arithmetic operations;
  4. Implementation of the calculator-web module. This module implements a standard MVC portlet that uses the APIs defined by the calculator-api module to perform the arithmetic operations.

 

Figure 1 - Diagram of the relationships between the modules

Figure 1 - Diagram of the relationships between the modules

The figure below shows the class diagram of the main classes of the various modules. In detail:

 

  • calculator-api
    • Calculator is the interface that defines the arithmetic operations between two integers;
  • calculator-service
    • CalculatorClientImpl implements the APIs defined by the Calculator interface and acts as a client to the SOAP Calculator service;
  • calculator-web
    • AddOperationMVCActionCommand: MVC Action Command of the Calculator Web Module for the request of the addition operation;
  • calculator-gogo-shell
    • CalculatorCommand: Gogo Shell Commands of the Calculator Gogo Shell Module.

 

Figure 2 - Class diagram

Figure 2 - Class diagram

 

1 - Implementation of the SOAP client

In order to call the SOAP service we need to create the stubs that will be our interface that will allow us to interact with the remote service through the SOAP protocol.

 

To create all the java code we need, starting from the WSDL document, we usually use the wsimport tool. Instead of using the command-line tool directly, we'll set up the project to use a Gradle task that does everything for us.

 

The one shown below is the build.gradle file of the calculator-service module. The parts highlighted make it possible to use the Gradle gradle-jaxws-plugin plugin.

 

The second block highlighted, sets the directory of WSDL documents and the directory that will contain the java files generated from the WSDL document. The WSDL document can be downloaded from the address http://www.dneonline.com/calculator.asmx?WSDL and saved in the directory indicated.
buildscript {
   repositories {
      maven {
         url "https://plugins.gradle.org/m2/"
      }
   }
   dependencies {
      classpath "gradle.plugin.cz.swsamuraj:gradle-jaxws-plugin:0.6.1"
   }
}

apply plugin: "cz.swsamuraj.jaxws"


dependencies {
   compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "4.4.0"
   compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0"
   compileOnly project(":modules:calculator:calculator-api")
}


jaxws {
   wsdlDir = 'src/main/resources/META-INF/wsdl'
   generatedSources = 'generatedsources/src/main/java'
}
After editing the build.gradle file you should (and after a refresh gradle project) the new Gradle tasks and in particular: wsImport
Figure 3 - New Gradle tasks added after configuring the gradle-jaxws-plugin plugin

Figure 3 - New Gradle tasks added after configuring the gradle-jaxws-plugin plugin

At this point all that remains is to run the wsImport task whose output will be the generation of the Java classes (stub). Now we have everything we need to be able to implement the SOAP client.
Figure 4 - Java classes generated by the Gradle wsImport task starting from the WSDL of the SOAP Calculator service.

Figure 4 - Java classes generated by the Gradle wsImport task starting from the WSDL of the SOAP Calculator service.

The code shown below is the implementation of the CalculatorClientImpl class that implements the Calculator interface and uses the stub classes to get the interface to SOAP services.

 

The _getService() method gets the reference to the CalculatorSOAP interface, the latter is used within all the methods that implement arithmetic operations.

 

Note that CalculatorClientImpl is a DS component, this will allow us to use the services of the Calculator interface with the simple @Reference annotation.
package it.dontesta.labs.liferay.webservice.calculator.client.soap;
...

/**
* @author Antonio Musarra
*/
@Component(immediate = true, property = {}, service = Calculator.class)
public class CalculatorClientImpl implements Calculator {


   @Override
   public int add(int number1, int number2)
      throws CalculatorOperationException {
      try {
         return _getService().add(number1, number2);
      }
      catch (CalculatorServiceException cse) {
         throw new CalculatorOperationException(cse.getMessage(), cse);
      }
   }


   @Override
   public int divide(int number1, int number2)
      throws CalculatorOperationException {
      try {
         return _getService().divide(number1, number2);
      }
      catch (CalculatorServiceException cse) {
         throw new CalculatorOperationException(cse.getMessage(), cse);
      }
   }


   @Override
   public int multiply(int number1, int number2)
      throws CalculatorOperationException {
      try {
         return _getService().multiply(number1, number2);
      }
      catch (CalculatorServiceException cse) {
         throw new CalculatorOperationException(cse.getMessage(), cse);
      }
   }


   @Override
   public int subtract(int number1, int number2)
      throws CalculatorOperationException {
      try {
         return _getService().subtract(number1, number2);
      }
      catch (CalculatorServiceException cse) {
         throw new CalculatorOperationException(cse.getMessage(), cse);
      }
   }
   private CalculatorSoap _getService() throws CalculatorServiceException {
      if (Validator.isNull(_calculatorSoap)) {
         try {
            org.tempuri.Calculator calculator =
               new org.tempuri.Calculator();
            _calculatorSoap = calculator.getCalculatorSoap();
         }
         catch (WebServiceException wse) {
            throw new CalculatorServiceException(wse.getMessage(), wse);
         }
         return _calculatorSoap;
      }
      else {
         return _calculatorSoap;
      }
   }

   private CalculatorSoap _calculatorSoap;
}

From this moment we can use the Calculator service on any module. Who uses the service is absolutely abstract from the fact that the implementation uses external SOAP services.

2 - Implementation of Gogo Shell commands

Now let's see how to use the service to create a series of Gogo Shell commands that allow us to perform arithmetic operations.

 

The following code shows the class CalculatorCommand which uses the Calculator service referenced by the @Reference annotation and implement the arithmetic operations we defined in the Calculator interface.

 

I have reported only the most significant parts of the code that are highlighted.
package it.dontesta.labs.liferay.webservice.calculator.gogoshell;

import it.dontesta.labs.liferay.webservice.calculator.api.Calculator;

...

/**
* @author Antonio Musarra
*/
@Component(
   property = {
      "osgi.command.function=add",
      "osgi.command.function=divide",
      "osgi.command.function=multiply",
      "osgi.command.function=subtract",
      "osgi.command.scope=calculator"
   },
   service = Object.class
)
public class CalculatorCommand implements Calculator {
   public int add(int number1, int number2)
      throws CalculatorOperationException {
      return getCalculatorService().add(number1, number2);
   }

   ...

   @Reference(policyOption = ReferencePolicyOption.GREEDY)
   private volatile Calculator _calculator;
}

Once the module has been deployed, we have the commands to make the arithmetic operations available. Just connect to the Gogo Shell and try.

Figure 5 - Division by zero!

Figure 5 - Division by zero!

 

Figure 6 - Error in case of connectivity problems

Figure 6 - Error in case of connectivity problems

3 - Implementation of Calculator Web

We have seen how to use the Calculator service for Gogo Shell commands, why not implement a simple very basic web calculator?

 

In the class diagram of figure 2 we have seen that there are four MVC Command, one for each operation.

 

Following is the MVC Command code responsible for performing the addition operation and making the result available to the view.
package it.dontesta.labs.liferay.web.calculator.portlet.action;

import it.dontesta.labs.liferay.webservice.calculator.api.Calculator;

...

/**
* @author Antonio Musarra
*/
@Component(
   immediate = true,
   property = {
      "javax.portlet.name=" + CalculatorAppPortletKeys.CALCULATOR_SOAP_APP,
      "mvc.command.name=/calculator/add-operation"
   },
   service = MVCActionCommand.class
)
public class AddOperationMVCActionCommand extends BaseMVCActionCommand {


   @Override
   protected void doProcessAction(
         ActionRequest actionRequest, ActionResponse actionResponse)
      throws Exception {


      _handleActionCommand(actionRequest);
   }


   private void _handleActionCommand(ActionRequest actionRequest) {
      try {
         int term1 = ParamUtil.getInteger(
            actionRequest, CalculatorAppWebKeys.ADD_OPERATION_TERM_1);


         int term2 = ParamUtil.getInteger(
            actionRequest, CalculatorAppWebKeys.ADD_OPERATION_TERM_2);


         int result = _calculator.add(term1, term2);


         actionRequest.setAttribute(
            CalculatorAppWebKeys.ADD_OPERATION_RESULT, result);
      }
      catch (SOAPFaultException soapfe) {
         SessionErrors.add(actionRequest, soapfe.getClass(), soapfe);
      }
      catch (WebServiceException wse) {
         SessionErrors.add(actionRequest, wse.getClass(), wse);
      }
      catch (Exception e) {
         SessionErrors.add(
            actionRequest, "calculatorOperationError", e.getMessage());
      }
   }

   @Reference(policyOption = ReferencePolicyOption.GREEDY)
   private Calculator _calculator;
}

The following figures show the simple calculator in action.

Figure 7 - Example of the GUI for the division operation

Figure 7 - Example of the GUI for the division operation

Figure 8 - Still division by zero! Example of the GUI in case of error obtained from the execution of the operation

Figure 8 - Still division by zero! Example of the GUI in case of error obtained from the execution of the operation

4 - Configuration of the JAX-WS API and CXF EndPoints

Everything we have seen so far could not work until these two components are configured on the Liferay system:
  • CXF EndPoints
  • JAX-WS API

 

This configuration should only be necessary if we decide to develop and publish SOAP services on our Liferay instance.

 

Even if we act as a SOAP client we must configure these two components so that the JAX-WS provider is correctly configured and we can use the JAX-WS API without incurring any error.

 

The one shown in the figure is the error we would see if we did not configure the two components from the Liferay control panel.
Figure 9 - Error shown when the JAX-WS API bridge has not been configured.

Figure 9 - Error shown when the JAX-WS API bridge has not been configured.

The following figures show the configurations for CXF and JAX-WS and in particular the Context Path (for more info you can see this https://portal.liferay.dev/docs/7-0/tutorials/-/knowledge_base/t/jax-ws-and-jax-rs).
Figure 10 - CXF Endpoint configuration

Figure 10 - CXF Endpoint configuration

 

Figure 11 - JAX-WS API configuration

Figure 11 - JAX-WS API configuration

 

If we want to be picky, we could run the services "(objectClass=javax.xml.ws.spi.Provider)" command from Gogo Shell to verify the JAX-WS API bridge has been properly configured. You should get it in output when shown in the figure below.

 

Figure 12 - Output of the command services "(objectClass=javax.xml.ws.spi.Provider)”

Figure 12 - Output of the command services "(objectClass=javax.xml.ws.spi.Provider)”

For more information on this aspect I recommend reading this LPS - Supplied JAX-WS implementation not working.

 

5 - Conclusions

In this article we saw how simple it is to implement a standard JAX-WS SOAP client using the JAX-WS infrastructure that Liferay makes available to us and whose implementation is achieved through the Apache CXF framework. Liferay 7.2 CE GA1 uses version 3.2 of Apache CXF.

 

In one of the next articles we will see more complex scenarios, where for example:

 

  • The SOAP service we want to use requires an SSL/TLS Mutual authentication
  • The SOAP service we want to access requires WS-Security

Antonio Musarra

I began my journey into the world of computing from an Olivetti M24 PC (http://it.wikipedia.org/wiki/Olivetti_M24) bought by my father for his work. Day after day, quickly taking control until … Now doing business consulting for projects in the enterprise application development using web-oriented technologies such as J2EE, Web Services, ESB, TIBCO, PHP.

You may also like...