Apache CXF Soap Service Implementation through Quarkus With Standalone, Docker and Kubernetes deployment

 

 To Support Apache CXF in Quarkus application the Quarkiverse cxf dependency to be added as part of pom.xml (Quarkiverse is a github organization for any contributors to develop Quarkus extensions). Since current version of Quarkus (2.2.9 final ) doesn't support Apache CXF. So we are going for Quarkiverse CXF dependency.

    <dependency>
<groupId>io.quarkiverse.cxf</groupId>
<artifactId>quarkus-cxf</artifactId>
<version>${quarkiverse.version}</version>
</dependency>

Lets create a simple SOAP service which will accept the enrollment number of a student and respond back the entire student information

Now lets start with creating schema classes

StudentInfo.java

package org.brktech.quarkus.cxfpoc.schema;

import java.io.Serializable;
import java.util.Objects;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlType(name = "Student")
@XmlRootElement
public class StudentInfo implements Serializable {

private static final long serialVersionUID = 3483155172700548490L;

private String firstName;
private String middleName;
private String lastName;
private String age;
private String stdClass;
private String classSection;
private String rollNumber;

public String getFirstName() {
return firstName;
}

@XmlElement
public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getMiddleName() {
return middleName;
}

@XmlElement
public void setMiddleName(String middleName) {
this.middleName = middleName;
}

public String getLastName() {
return lastName;
}

@XmlElement
public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getAge() {
return age;
}

@XmlElement
public void setAge(String age) {
this.age = age;
}

public String getStdClass() {
return stdClass;
}

@XmlElement
public void setStdClass(String stdClass) {
this.stdClass = stdClass;
}

public String getClassSection() {
return classSection;
}

@XmlElement
public void setClassSection(String classSection) {
this.classSection = classSection;
}

public String getRollNumber() {
return rollNumber;
}

@XmlElement
public void setRollNumber(String rollNumber) {
this.rollNumber = rollNumber;
}

@Override
public String toString() {
return "StudentInfo [firstName=" + firstName + ", middleName=" + middleName + ", lastName=" + lastName
+ ", age=" + age + ", stdClass=" + stdClass + ", classSection=" + classSection + ", rollNumber="
+ rollNumber + "]";
}

@Override
public int hashCode() {
return Objects.hash(age, classSection, firstName, lastName, middleName, rollNumber, stdClass);
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
StudentInfo other = (StudentInfo) obj;
return Objects.equals(age, other.age) && Objects.equals(classSection, other.classSection)
&& Objects.equals(firstName, other.firstName) && Objects.equals(lastName, other.lastName)
&& Objects.equals(middleName, other.middleName) && Objects.equals(rollNumber, other.rollNumber)
&& Objects.equals(stdClass, other.stdClass);
}

}

GetStudentByRollNumberRequest.java

package org.brktech.quarkus.cxfpoc.schema;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlType(name = "GetStudentInfoByRollNumberRequest")
@XmlRootElement
public class GetStudentByRollNumberRequest {


private String rollNumbber;

public String getRollNumbber() {
return rollNumbber;
}
@XmlElement
public void setRollNumbber(String rollNumbber) {
this.rollNumbber = rollNumbber;
}

@Override
public String toString() {
return "GetStudentByRollNumberRequest [rollNumbber=" + rollNumbber + "]";
}

}

StudentInformationResponse.java

package org.brktech.quarkus.cxfpoc.schema;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlType(name = "StudentInformationResponse")
@XmlRootElement
public class StudentInformationResponse implements Serializable {

private static final long serialVersionUID = -3016596112656167156L;


private List<StudentInfo> studentInformation;

public List<StudentInfo> getStudentInformation() {
if (studentInformation == null) {
studentInformation = new ArrayList<>();
}
return studentInformation;
}
@XmlElement
public void setStudentInformation(List<StudentInfo> studentInformation) {
this.studentInformation = studentInformation;
}

@Override
public String toString() {
return "StudentInformationResponse [studentInformation=" + studentInformation + "]";
}

}

Lets Create a Service Interface as below

StudentInfoService.java

package org.brktech.quarkus.cxfpoc.service;

import javax.jws.WebMethod;
import javax.jws.WebService;

import org.brktech.quarkus.cxfpoc.schema.GetStudentByRollNumberRequest;
import org.brktech.quarkus.cxfpoc.schema.StudentInformationResponse;

@WebService
public interface StudentInfoService {

@WebMethod
public StudentInformationResponse getStudentinformationByRollNuber(
GetStudentByRollNumberRequest getStudentByRollNumberRequest);

}

Now lets create a StudentInfo data loader class which holds the sample StudentInfo as a map.

StudentInfoLoader.java

package org.brktech;

import java.util.HashMap;
import java.util.Map;

import org.brktech.quarkus.cxfpoc.schema.StudentInfo;

public class StudentInfoLoader {

public static Map<String, StudentInfo> getAllStudentInfoAsMap() {
StudentInfo info1 = new StudentInfo();
info1.setFirstName("Anshuman");
info1.setMiddleName("");
info1.setLastName("Mohapatra");
info1.setStdClass("5");
info1.setClassSection("A");
info1.setAge("9");
info1.setRollNumber("tech100");

StudentInfo info2 = new StudentInfo();
info2.setFirstName("Anil");
info2.setMiddleName("");
info2.setLastName("Vunnava");
info2.setStdClass("5");
info2.setClassSection("A");
info2.setAge("9");
info2.setRollNumber("tech101");

StudentInfo info3 = new StudentInfo();
info3.setFirstName("Bharath");
info3.setMiddleName("");
info3.setLastName("Ravi");
info3.setStdClass("5");
info3.setClassSection("A");
info3.setAge("9");
info3.setRollNumber("tech103");

Map<String, StudentInfo> studentinfoMap = new HashMap<>();
studentinfoMap.put(info1.getRollNumber(), info1);
studentinfoMap.put(info2.getRollNumber(), info2);
studentinfoMap.put(info3.getRollNumber(), info3);

return studentinfoMap;
}

}

Lets create a Service Implementation class below

StudentInfoServiceImpl.java

package org.brktech.quarkus.cxfpoc.service.impl;

import java.util.Map;
import java.util.Map.Entry;

import org.brktech.StudentInfoLoader;
import org.brktech.quarkus.cxfpoc.schema.GetStudentByRollNumberRequest;
import org.brktech.quarkus.cxfpoc.schema.StudentInfo;
import org.brktech.quarkus.cxfpoc.schema.StudentInformationResponse;
import org.brktech.quarkus.cxfpoc.service.StudentInfoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StudentInfoServiceImpl implements StudentInfoService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private final Map<String, StudentInfo> studentInfoAsMap;

StudentInfoServiceImpl() {
studentInfoAsMap = StudentInfoLoader.getAllStudentInfoAsMap();
logger.info("Student Info Map Loaded");
logger.info("************************************************************");
for (Entry<String, StudentInfo> stdInfo : studentInfoAsMap.entrySet()) {
logger.info("Roll No. :: " + stdInfo.getKey());
logger.info("Student Info. :: " + stdInfo.getValue());
}
logger.info("************************************************************");
}

@Override
public StudentInformationResponse getStudentinformationByRollNuber(
GetStudentByRollNumberRequest getStudentByRollNumberRequest) {
StudentInformationResponse informationResponse = new StudentInformationResponse();
if (getStudentByRollNumberRequest != null) {
StudentInfo info = studentInfoAsMap.get(getStudentByRollNumberRequest.getRollNumbber());
if (info != null) {
logger.info(
"Student Info record found with Roll No : " + getStudentByRollNumberRequest.getRollNumbber());
logger.info("Student info record :: " + info);
informationResponse.getStudentInformation().add(info);
}
}
return informationResponse;
}
}

to expose the service properties to be added as part of application.properties file

application.properties

#override the default http port 8080 
quarkus.http.port=8282
#path where cxf services are listed
quarkus.cxf.path=/cxf
#/studentinfoservice is where wsdl is exposed example
$http://localhost:8282/cxf/studentinfoservice?wsdl
quarkus.cxf.endpoint."/studentinfoservice".implementor=org.brktech.quarkus.cxfpoc.service.StudentInfoServiceImpl

Standalone deployment

Lets build the source through maven with the below command 

mvn clean package

 We can see the source is build successfully as below 


now go to the target/quarkus-app directory (projectfolder/target/quarkus-app) and execute the below command

java -jar quarkus-run.jar

Now we can see application is started

we can now see the list of services exposed through http://localhost:8282/cxf through any browser

we can access the studentinfoservice through the below URL http://localhost:8282/cxf/studentinfoservice?wsdl

 

Docker Deployment

Quarkus application by default provides us the capability on deploying the source to docker container on successful source build, all we need to is to pass an additional optional parameter "-Dquarkus.container-image.build=true" while building the source, on successful build quarkus will create an docker image with our application and deploy the image  in the docker container (docker to be installed before performing this activity)

mvn clean package -Dquarkus.container-image.build=true

 

we can see now a docker container image is created 

We can also check the docker images list in our local docker container through "docker images" command

docker images

now we can run the container image by executing the below command

docker run --rm -p 8282:8282 -p 5005:5005 bharaniravikanth/quarkus-cxf-soap-webservice:0.0.1-SNAPSHOT

We can also push our docker container image to docker registry on successful source build, we need to pass optional parameter "-Dquarkus.container-image.push=true" through maven package command.

mvn clean package -Dquarkus.container-image.push=true

Kubernetes Deployment

Prerequisites:

1. Docker

2. Kubernetes cluster

lets create a YAML file with deployment and service information 

Deployments

apiVersion: apps/v1
kind: Deployment
metadata:
name: quarkus-cxf-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
brktech: web
template:
metadata:
labels:
brktech: web
spec:
containers:
- name: quarkus-cxf-demo-site
image: bharaniravikanth/quarkus-cxf-soap-webservice:0.0.1-SNAPSHOT

on deployment Kubernetes will create only one pod (replicas value is 1)

Service

apiVersion: v1
kind: Service
metadata:
name: quarkus-cxf-demo-service
namespace: default
spec:
selector:
brktech: web
ports:
- name: http
nodePort: 30475
port: 8787
protocol: TCP
targetPort: 8282
clusterIP: 10.96.0.12
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 192.168.0.104

As per the service configuration Kubernetes will expose the our application through the port 8787 which internally redirects to the port 8282 (Target port), since the service type is LoadBalancer, when we scale the pods, Kubernetes handles the load balancing automatically between the pods .

the final yaml file looks as below 

apiVersion: apps/v1
kind: Deployment
metadata:
name: quarkus-cxf-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
brktech: web
template:
metadata:
labels:
brktech: web
spec:
containers:
- name: quarkus-cxf-demo-site
image: bharaniravikanth/quarkus-cxf-soap-webservice:0.0.1-SNAPSHOT
---
apiVersion: v1
kind: Service
metadata:
name: quarkus-cxf-demo-service
namespace: default
spec:
selector:
brktech: web
ports:
- name: http
nodePort: 30475
port: 8787
protocol: TCP
targetPort: 8282
clusterIP: 10.96.0.12
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 192.168.0.104

to deploy in Kubernetes through yaml file we need to execute the command below

kubectl apply -f fileName.yaml

I have created a yaml file with quarkus_cxf_poc.yaml, so our kubectl apply command will be as follows

On successful execution we get response as deployment is created, service is created

Once the yaml is applied we can check the deployments, services and pods through command line or Kubernetes dashboard

Check deployments the command below 

kubectl get deployments

Check deployments through Kubernetes dashboard 

Check Services through command line

kubectl get deployments

Check Services through dashboard

Since the replicas are set to 1 the number of pods (are the light weight containers which responsible for running our application) created are one 

Once the Kubernetes deployment is complete and our services are up we can access our cxf application through through the port 8787 (which was part of yaml file)

And Wsdl will be exposed through the url http://localhost:8787/cxf/studentinfoservice?wsdl

To test our application I am using SOAP UI

Lets create a soap project with our wsdl http://localhost:8787/cxf/studentinfoservice?wsdl

Once the project is created the Structure looks as below 

click on the request and pass the request parameter and hit the request through the url http://localhost:8787/cxf/studentinfoservice 

we can see the response back under response section of the request

to check the logs of the application in kubernetes, we can check through command line or dashboard

Checking logs through command line

kubectl logs <pod-name>

Checking logs through dashboard

Login to dashboard and click on Pods and select the pod menu (vertical dots) and click on logs, Dashboard will go inside the pod and displays the logs of the application running through the pod.

You can find the entire project through my github repository 

https://github.com/bharaniravikanth/camel-quarkus-brk-tech-repo/tree/main/camel-quarkus-integration/quarkus-cxf-soap-webservice

Share this

Latest
Pages
Previous
Next Post »

1 comments:

Write comments
9 January 2023 at 12:30 delete This comment has been removed by the author.
avatar