Apache CXF is an open source services framework. CXF helps us to build and develop services using frontend programming APIs, like JAX-WS and JAX-RS. These services can speak a variety of protocols such as SOAP, XML/HTTP, RESTful HTTP, or CORBA and work over a variety of transports such as HTTP, JMS or JBI.
Some of the key features of CXF include support for the latest web service standards, data transformation, interceptors, security, and RESTful services. CXF also integrates well with other popular Java technologies like Spring and Camel.
CXF implements the JAX-WS APIs which make building web services easy. JAX-WS encompasses many different areas:
Generating WSDL from Java classes and generating Java classes from WSDL
Provider API which allows you to create simple messaging receiving server endpoints
Dispatch API which allows you to send raw XML messages to server endpoints
Much more...
Spring is a first class citizen with Apache CXF. CXF supports the Spring 2.0 XML syntax, making it trivial to declare endpoints which are backed by Spring and inject clients into your application.
So you need go to country list SOAP service by your browser. Then Right Click and choose Save As. After that you save the file with type .wsdl into any package in src/main/resources of your spring boot project as the images below:
<build><plugins><plugin><groupId>org.apache.cxf</groupId><artifactId>cxf-codegen-plugin</artifactId><version>${cxf.version}</version><executions><execution><id>generate-sources</id><phase>generate-sources</phase><configuration><!-- the source root that will contain our generated java classes --><sourceRoot>${project.build.directory}/generated-sources/java</sourceRoot><wsdlOptions><wsdlOption><!-- the path where we put the wsdl file --><wsdl>${basedir}/src/main/resources/countries.wsdl</wsdl><!-- the package that contains our generated java classes --><packagenames>com.springboot.apache.cxf.generated</packagenames></wsdlOption></wsdlOptions></configuration><goals><!-- convert wsdl file to java classes --><goal>wsdl2java</goal></goals></execution></executions></plugin></plugins></build>
So let's open the terminal/command line then run mvn compile, then Maven will read the file countries.wsdl to generate java classes and put them into the package that we have already defined in the pom.xml.
If you note that, java classes have been configured with many annotation of XML, So these class will be formatted as xml type when we transfer them to SOAP service.
Firstly, we will need to create a configuration properties class to load some needed properties that we will configure in the application.yml file by using @ConfigurationProperties, if you have not used it before then you can view Spring Boot With @ConfigurationProperties.
So let's create CountryInfoProperties.java which will load some properties: hostname, protocol, port and api as below.
So if you look into the URL that we used to download the wsdl file we can know the protocol (No SSL so http), host , port (No SSL so the port is 80) and the api and we will put them into the configuration above.
Next, we will configure the client for calling Soap WebServices. If you look into the generated code by Apache CXF you can see there is an interface like this.
So this interface represents a SOAP WebService client that communicates with a WebService provider. If you look into every method you can see each method has annotations such as @WebMethod, @WebParam, @WebResult, @RequestWrapper, and @ResponseWrapper. These annotations are used to specify the SOAP message format and details.
Then we also see there is a generated java class that is annotation with annotation @WebServiceClient as below.
So this class CountryInfoService is a generated WebService client that allows Java applications to interact with a SOAP-based WebService. The purpose of this class is to provide a Java interface for a client to access the web service's functions to retrieve information. If you look into this class, you can see the method getPort will return the SOAP WebService client interface above.
Now, We will configure it for calling SOAP WebService. Let's create a configuration class for SOAP WebService client as below.
Firstly, we will create an instance of CountryInfoService. Then from this instance we will get the instance CountryInfoServiceSoapType. Then we will set the request URL for it by using the UrlFactory that we created in the step above and return it as a bean countryInfoServiceSoapType.
Secondly, we will create a bean countryInfoServiceSoapTypeFactory with new instance ObjectFactory. In Apache CXF, the ObjectFactory is a class that is generated from the XML schema file and is used to create instances of the JAXB-generated classes that represent the schema elements.
In other words, the ObjectFactory is responsible for creating instances of the classes that correspond to the XML elements defined in the schema. This is necessary because JAXB classes are typically generated as plain Java classes without any constructors. Instead, JAXB provides the ObjectFactory class to create instances of these classes.
By creating a bean for the ObjectFactory, we can inject it into other beans or components that require it to create instances of the JAXB-generated classes. This can be useful when working with SOAP web services or other XML-based technologies that rely on JAXB to serialize and deserialize XML data.
An utility class is helpful for us to use for mapping request objects for calling Soap WebService or response objects from Soap WebService. So let's create the Utility java class as below.
In this CountryInfoFactory utility class we will have two methods which are used for mapping result objects from Soap WebService to our response DTO classes.
Now, let's create a service class for calling Soap WebService by using the configured Soap WebService client that we configured before. Now, we will inject the CountryInfoServiceSoapType client into our service and use it as below.
There are many api that we can use in CountryInfoServiceSoapType but in this example we just use 2 of them, they are countryName and listOfCountryNamesGroupedByContinent.
Okay, we have finished implementing the happy case, but for the case that we getting errors from the Soap WebService like Service is unavailable or not found, how we can handle it?
Let's take an example, if we change api or the hostname in the application.yml and call the Soap WebService again, you will always receive the 500 error as below.
So, this is not true and it is hard for developers to detect the issues when working with Soap WebService. Then handling the exception with Apache CXF is important and we should practice with it.
Now, We will create an exception handler class named GlobalExceptionHandler which will extends from ResponseEntityExceptionHandler class, by default the ResponseEntityExceptionHandler will provide an @ExceptionHandler method for handling internal Spring MVC exceptions.
As you can see, we will create 2 methods. The first method technicalExceptionHandler is used for catching all exceptions with type TechnicalException and then we will extracted information from it for building response body which is the ErrorDetail DTO class. Like wise, the second method exceptionHandler is used for catching all other exception types which are not TechnicalException.
Next, we will need to update the utility class for extracting information and building the TechnicalException. So let's add some more methods buildTechnicalException, getResponseCode, getEndpoint and getResponseHeaders as below.
So, to build the TechnicalException, we will need to extract some information from failed response when we call to the SOAP WebService. To to that we can cast the CountryInfoServiceSoapType SOAP WebService client interface from the input param to the BindingProvider. Then from the BindingProvider we can easily use getRequestContext or getResponseContext for extracting information from the request or response of the SOAP WebService call respectively.
We also need the Exception as an input param to extract the error message and rootCause.