Friday, January 5, 2018

SOAP Web Services in WildFly

Web services are client and server applications that communicate with each other through the HyperText Transfer Protocol (HTTP). As the name implies, web services represent something accessible on the web that gives you a service. The application that gives a service is called the provider and the one that uses a service is called consumer.

The main advantage of web services is that they provide a standard means of interoperating between software applications running on a variety of platforms and frameworks, i.e., the underlying implementation can be done in any language (Java, PHP, C#, C++, ...). A consumer and a service provider will still be able to exchange data in a loosely coupled way using XML documents.

SOAP (Simple Object Access Protocol) web services are said to be loosely coupled because the client doesn't have to know its implementation details. The communication between them happens by means of XML messages that rely on the SOAP specification which refers to the WSDL (Web Services Description Languages).  The Web Service Description Language is the de facto standard to provide a description of a web service contract exposed to clients. If you want to use (consume) some SOAP based web services you must follow the rules described in its XML, the WSDL document, that describes a web service in terms of the operations that it provides, and the data types that each operation requires as input and can return in the form of results.

Creating a SOAP Web Service in WildFly 10 to validate a CPF

 Open Eclipse IDE and create a New Maven Project. Give it the name SOAP_WS_ValidateCPF, select packaging as .war and Finish:




In the pom.xml add the dependency for JavaEE 8 API. It should be like this:
<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/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>SOAP_WS_ValidateCPF</groupId>
  <artifactId>SOAP_WS_ValidateCPF</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <properties>
 <failOnMissingWebXml>false</failOnMissingWebXml>
 <maven.compiler.source>1.8</maven.compiler.source>
 <maven.compiler.target>1.8</maven.compiler.target>
 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
 <dependency>
  <groupId>javax</groupId>
  <artifactId>javaee-api</artifactId>
  <version>8.0</version>
 </dependency>
  </dependencies>
</project>

Although it's not mandatory, we will create an interface to represent our web service contract. It will have a method that receives a Pessoa as an argument and checks if its CPF field is valid by setting a flag in the field cpfValido, then returns that person to the consumer:
public interface ConsultaCPF {

 public Pessoa consultarCPF( Pessoa pessoa);
}
And the interface implementation ConsultCPFImpl. We turn that class into a web service just by adding the mandatory @WebService annotation, all others annotations are optional.
@WebService(targetNamespace="http://www.company.com/", serviceName="ConsultaCPFService")
public class ConsultaCPFImpl implements ConsultaCPF {

 @Override
 @WebMethod
 @WebResult(name = "pessoa")
 public Pessoa consultarCPF( @WebParam(name = "pessoa") Pessoa pessoa) {
  
  //business rules...
  if(pessoa.getCpf() != null && pessoa.getCpf().length() == 11 ){
   
   String cpf = pessoa.getCpf();
   char ultimoDigito = cpf.charAt(cpf.length()-1);
   int n = new Integer(ultimoDigito);
   boolean par = n % 2 == 0;
   pessoa.setCpfValido(par);   
  }
  else
   pessoa.setCpfValido(false);
  
  return pessoa;
 }
}
The hypothetical validation rule just checks if the field is not null and has 11 characters, then validates if the last character is even. The meaning of the other annotations and its parameters are:
  • @WebService(targetNameSpace="xyz"): declare the namespaces for WSDL elements generated by the web service
  • @WebService(serviceName="name"): the name specified is used to generate the name attribute in the service element in the WSDL interface
  • @WebMethod(): indicates that you want to expose this public method as web service
  • @WebParam(): indicates the parameter name that should be exhibited in the WSDL
  • @WebResult(): similar to @WebParam, it specifies the method param name in the WSDL
Of course, we need the entity Pessoa. As stated before, everything exchanged between the consumer and the web service take place as XML file, that means things like java POJOs should be marshaled/unmarshaled to/from an XML format as it is sent/received by consumer and provider. JAXB API does that behind the scenes, all we need to do is annotate the classes and its fields that will be exchanged like following:

@XmlRootElement(name = "pessoa")
@XmlAccessorType(XmlAccessType.FIELD)
public class Pessoa {
 
 @XmlAttribute(name = "nome")
 private String nome;

 @XmlAttribute(required = true)
 private String cpf;

 @XmlAttribute(name = "nascimento")
 private Date nascimento;

 @XmlAttribute(name = "cpfValido")
 private boolean cpfValido;

      //getters & setters...

That's all we need. Now right click on your project, go to properties, then project facets at the right panel, click the runtime tab in the right panel and choose wild fly:


Now right click on your project, choose run as, run on  server... (you might need to clean and build your project or restart wildfly). Check WildFly log and look for this part confirming your web service is already published:

As a compliant JavaEE container, WildFly recognizes, by the @WebService annotation, that your project has web services and automatically creates and publishes the WSDL document. Open WildFly admin console (generally at port 9990), click Runtime tab and navigates through Standalone Server, Subsystems, Web Services and click in view:


Click on WSDL Url to see the generated WSDL XML document exposed representing your web service:



Invoking the Web Service

Since our web service is up and running, it is time to consume it. Make sure to not shutdown wildfly, otherwise the web service won't be accessible anymore.

Create a simple new Java project named SOAP_WS_ValidateCPF_Consumer. Create a class named Main with the main method. At this point, the new project doesn't know nothing about the ConsultaCPFService web service. Java has the utility wsimport under <JAVA_HOME>/bin folder to create the needed artifacts that allow some project to consume a specified web service.

Open your terminal command and go to the source folder of SOAP_WS_ValidateCPF_Consumer project (in my case /home/rafael/workspace/SOAP_WS_ValidateCPF_Consumer/src/) and execute the wsimport utility passing the WSDL Url as a parameter like the picture (you might need root privileges if you are in a Linux environment):



/usr/lib/jvm/jdk1.8.0_144/bin/wsimport -keep -verbose http://localhost:8080/SOAP_WS_ValidateCPF-0.0.1-SNAPSHOT/ConsultaCPFService?wsdl

The wsimport utility imports all needed artifacts to invoke the web service. Refresh your project on eclipse and see that new packages were created in accord with WSDL contract at the URL passes as a parameter:



Now the Main class looks like:

package main;

import com.company.ConsultaCPF;
import com.company.ConsultaCPFService;
import com.ecommerce.ws.consulta_cpf.Pessoa;

public class Main {
 
 //we can get a reference to web service by calling the method getConsultaCPFImplPort()
 ConsultaCPF consultaCPF = new ConsultaCPFService().getConsultaCPFImplPort();
 
 public static void main(String[] args) {
  
  new Main();    
 }
 
 public Main(){
  
  System.out.println(consultaCPF);
  Pessoa pessoa = new Pessoa();
  pessoa.setCpf("55555555551");
  System.out.println(consultaCPF.consultarCPF(pessoa).isCpfValido());
 }
}

Run the project. It will return true or false depending on the last digit of CPF field. This small standalone class has shown how it is possible to use SOAP-based services from the client-side perspective.

Now, shutdown wildfly and try to run the Main again. An exception is thrown because the service is not accessible anymore:
Exception in thread "main" javax.xml.ws.WebServiceException: Failed to access the WSDL at: http://localhost:8080/SOAP_WS_ValidateCPF-0.0.1-SNAPSHOT/ConsultaCPFService?wsdl. It failed with: 
 Connection refused (Connection refused).
 at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.tryWithMex(RuntimeWSDLParser.java:250)
 at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:231)
 at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:194)
 at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:163)
 at com.sun.xml.internal.ws.client.WSServiceDelegate.parseWSDL(WSServiceDelegate.java:348)
 at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:306)
 at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:215)
 at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:196)
 at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:192)
 at com.sun.xml.internal.ws.spi.ProviderImpl.createServiceDelegate(ProviderImpl.java:104)
 at javax.xml.ws.Service.<init>(Service.java:77)
 at com.company.ConsultaCPFService.<init>(ConsultaCPFService.java:42)
 at main.Main.<init>(Main.java:10)
 at main.Main.main(Main.java:14)

References:
GONCALVES, Antonio . Beginning Java EE 7 (Expert Voice in Java). 1. ed. New York: Apress, 2013. 608 p.
ĆMIL , Michal; MATLOKA, Michal ; MARCHIONI, Francesco . Java EE 7 Development with WildFly. 2. ed. Birmingham: Packt Publishing Ltd., 2013
JENDROCK, Eric et al. Java Platform, Enterprise Edition: The Java EE Tutorial Release 7. 1. ed. [S.l.]: Oracle, 2014. 980 p.