Note:

Create an Oracle GraalVM based Java Application using Spring Boot on OKE to Store SOAP Messages in ATP and Send to OCI Queue

Introduction

Many of our customers rely on traditional Simple Object Access Protocol (SOAP) messaging to enable communication between their applications. They often need to store transactional data, ensure service decoupling, achieve incoming requests load balancing, and enable asynchronous communication of these messages.

In this tutorial, we will see how we can build an Oracle GraalVM based Java application using Spring Boot deployed on the Oracle Cloud Infrastructure Kubernetes Engine (OKE) infrastructure that will be used as transactional integrator with the different Oracle Cloud Infrastructure (OCI) services such as Oracle Autonomous Transaction Processing (ATP) and OCI Queue. This setup allows systems to interact without direct connections, facilitating communication between applications with different processing speeds. Additionally, once the information is stored in the database, it can be consulted or analyzed.

We will use the following technologies:

Oracle Cloud Infrastructure Services (OCI): OCI is a secure, high-performance cloud platform offering more than 150-plus cloud services. It is designed for scalability, security, and performance.

Oracle Technology:

Other Technologies:

OCI High Level Architecture:

OCI Architecture

Use Case Architecture

Note:

Objectives

Prerequisites

Task 1: Provision and Configure OKE Cluster

In this task, we will provision the Kubernetes platform, where the application will support all the SOAP high transactionality messages to store in ATP and send each one to OCI Queue in real time.

  1. Log in to the OCI Console, navigate to Developer Services, Kubernetes Clusters (OKE), and select Compartment of your preference.

    There are two ways to create an OKE cluster:

    • Quick Create.
    • Custom Create.
  2. Select Quick create as this method is easier, faster and automatically deploys all the elements required by OKE for its operation, such as:

    • Virtual Cloud Network (VCN).
    • Internet Gateway.
    • Network Address Translation (NAT) gateway.
    • Service gateway.
    • Kubernetes cluster.
    • Kubernetes worker nodes(s) and node pool.

    Note: For the enterprise environments, where already the customers have services, network, infrastructure, it is important customize the OKE deployment, to be in compliance, aligned with the client architecture, resources and following the best practices.

    Quick Create

  3. Click Create cluster and enter the following information.

    • Name: Enter the name of OKE cluster.
    • Compartment: Select the compartment created for this project.
    • Kubernetes version: Select the latest Kubernetes version available.
    • Kubernetes API endpoint: In this tutorial, select Public endpoint, but you can also select Private endpoint.
    • Node Type: Select Managed nodes.
    • Kubernetes worker nodes: Select Private workers.
    • Shape and image: Select VM.Standard.E5.Flex, customize the number of OCPUs (2) and memory (16GB) and conserve default Oracle Linux 8 image.
    • Node Count: Enter 2 worker nodes to be deployed with OKE node pool.

    Create OKE Cluster

    Review that the OKE cluster is working.

    OKE Running

Task 2: Provision, Configure and Access to OCI Container Registry Classic

We need to manage the project images in a repository. To achieve this, we will provision the OCI Container Registry Classic. Once the images are stored in the OCI Container Registry Classic, we will be able to deploy them in OKE.

  1. Go to the OCI Console, navigate to Developer Services, Containers & Artifacts, Container Registry and click Create Repository.

  2. Enter the following information and click Create.

    • Create in Compartment: Select the compartment created for this project.
    • Access: Select Public.
    • Repository name: Enter springboot/tutorialapp.

    Create Repository

  3. Once the repository is created, from your Oracle admin host access it using the following command.

    docker login -u 'tenancy_namespace/domain/username' regionID.ocir.io
    
    Password: xxxxxx
    

    Oracle Registry Authentication

    OK

Task 3: Provision and Configure Oracle Autonomous Transaction Processing (ATP) Serverless Database

In ATP database, we will store the data of each SOAP message received per transaction, approximately each one will be in the order of milliseconds, attending to parallel and sequential insertions.

  1. Go to the OCI Console, navigate to Oracle Database and click Autonomous Transaction Processing.

  2. Click Create Autonomous Database and enter the following information.

    • Select the Compartment: Select the compartment created for this project.
    • Display Name: Enter the display name.
    • Display Database Name: Enter the database name.
    • Choose a workload type: Select Transaction Processing.
    • Choose a deployment type: Select Serverless.
    • Configure the database:
      • Developer: Deselect it.
      • Choose database version: Select 23ai.
      • ECPU count: Enter 2.
      • Compute auto scaling: Select it.
      • Storage: Enter 1024GB.
    • Automatic backup retention period in days: Leave the default option 60 days.
    • Create administrator credentials:
      • Username: Is ADMIN by default and it cannot be edited.
      • Password: Enter your preferred password.
      • Confirm password: Enter the password again.
    • Choose network access: Select Private endpoint access only then choose the VCN and subnet created for this project. This setting restricts connections to the specified private network (VCN) only. However, you can choose other options, this depends on the needs of the company.

    Create Oracle ATP Database

    Create Oracle ATP Database

    Create Oracle ATP Database

    Review that the ATP database is running.

    ATP Running

Task 4: Connect and Create Project Tables in Oracle Autonomous Transaction Processing (ATP)

Now, we need to configure, connect and create the project tables in the ATP database that was generated in Task 3.

  1. Go to the OCI Console, navigate to Oracle Database, Autonomous Transaction Processing and click Database Connection. Select TLS authentication, TLS and click Download Wallet.

    Database Connection

  2. Unzip the wallet .zip file and in tnsnames.ora, you can get the datasource URL to get the connection to this database. Save this datasource URL.

    For example:

    tutorialoracleatp_medium = (description= (retry_count=20)(retry_delay=3)(address=(protocol=tcps)(port=1522)(host=xxxxxxxx.adb.sa-saopaulo-1.oraclecloud.com))(connect_data=(service_name=xxxxxxxxxxxxxxx_tutorialoracleatp_medium.adb.oraclecloud.com))(security=(ssl_server_dn_match=no)))
    
  3. Access to the database is now necessary. When the ATP was provisioned, Oracle REST Data Services (ORDS) access was enabled. For more information, see Oracle REST Data Services.

    Go to the Autonomous Database details page, click Database actions. Note that it can only accessed from a compute instance running in the same virtual cloud network (VCN).

    Database actions

  4. Paste the URL in the browser and access to ORDS using the user and password entered in the ATP database previously and access the Oracle SQL Developer Web module.

    ORDS

  5. Use the following queries to create the tables USERS, CARS and HOUSES related with the SOAP messages that we will receive.

    CREATE TABLE USERS (
    username varchar(50) NOT NULL,
    userlastname varchar(50) NOT NULL,
    id int NOT NULL,
    email varchar(50) NOT NULL,
    dateuser varchar(50) NOT NULL,
    attributeuser varchar(50) NOT NULL  
    );
    
    CREATE TABLE CARS (
    userid int NOT NULL,
    brand varchar(50) NOT NULL,
    color varchar(50) NOT NULL,
    plate varchar(50) NOT NULL  
    );
    
    CREATE TABLE HOUSES (
    userid int NOT NULL,
    floors int NOT NULL,
    locationhouse varchar(50) NOT NULL,
    rooms int NOT NULL,
    bathrooms int NOT NULL
    );
    

    Tables created in the database

Task 5: Provision and Configure OCI Queue

In the OCI Queue, we will store highly transactional messages through RESTful HTTP API for a specific period of time. Consumers can then read and delete the messages immediately or at their convenience, ensuring decoupling and preventing data loss.

  1. Go to the OCI Console, navigate to Developer Services, Application Integration and click Queues.

  2. Click Create queue, enter the following information and click Create queue.

    • Name: Enter the name adequate for your queue.
    • Compartment: Select your work compartment.
    • Queue settings: In this tutorial, we will select Default configuration, but if you prefer, you can customize multiple options according to business needs such as: Visibility timeout, Maximum retention period, Maximum channel consumption and Dead letter queue settings.
    • Configure encryption settings: Select Oracle-managed key, but Customer-managed key is also an option.

    Create Queue

    Review that the OCI Queue is running.

    OCI Queue Running

Task 6: Build Oracle GraalVM based Java application with Spring Boot and Deploy it in OKE

Now, we will develop and deploy an Oracle GraalVM based Java application on Spring Boot that will perform the following tasks:

Note: Before starting, it is important to have an administration host and development environment created as shown in Prerequisites - Administration Host and Prerequisites - Development Environment section.

Once your admin host and development environment are configured and are ready, you can start to develop your Spring Boot project.

  1. Go to Spring initializr and create first project that will give us the folders structure and base files of a Spring Boot project, to later modify it according to our requirements. Enter the following information and click Generate, this will automatically download the Spring Boot project, save and unzip it in your development host.

    • Project: Select Maven.
    • Language: Select Java.
    • Spring boot: Select 3.3.6.
    • Project Metadata:
      • Group: Enter com.tutorial_springboot.
      • Artifact: Enter tutorial.
      • Name: Enter tutorial.
      • Description: Enter Spring Boot Application (read SOAP, transform to JSON, insert ATP and Put in OCI Queue).
    • Packaging: Select Jar.
    • Java: Select 17.
    • Dependencies: Select Oracle Driver, Spring Web Services and Spring Web.

    Note: We can add some dependencies in the project, and later add more, directly in the pom.xml file, according our needs.

    Springboot Project

  2. Now we have the spring boot structure project.

    Structure Spring boot project

    Review the pom.xml file, we will start to work on it.

    Original POM file

    Update the pom.xml file, according to the scope proposed in this tutorial.

    • Add the oci sdk version and the following dependencies.

      • oci-java-sdk-common.
      • oci-java-sdk-queue.
      • oci-java-sdk-addons-oke-workload-identity.
      • oci-java-sdk-common-httpclient-jersey3.

      The application will need to authenticate with OCI, connect and manage OCI services, such as OCI Queue.

    • Already our pom.xml file has spring-boot-starter-web-services dependency and we have to add wsdl4j dependency. The main purpose is to get the data received from SOAP messages and put it in Java objects, creating spring web services manipulating XML payloads, aims to facilitate contract-first SOAP service development. Also allows configure port, URI and set XML schema loaded from XML Schema Definition (XSD) file.

    • Add JSON dependency. This library will be used to generate JSON format with the data extracted from SOAP message.

    • Add spring-boot-maven-plugin plugin in Build section. This plugin will allow us to generate the jar executable Spring Boot project file.

    • Add jaxb2-maven-plugin plugin in Build section. This plugin will use Java API for XML binding (JAXB), to generate Java classes from XML schemas and in this way, we can pass the data from the SOAP message to Java class objects created by us.

      In this plugin section, it is important to put the configuration that will indicate the path, where the XSD file is included in our Spring Boot project.

      <configuration>
         <sources>
         <source>${project.basedir}/src/main/resources/messages.xsd<source>
         </sources>
      </configuration>
      
    • Add jasypt-spring-boot-starter dependency in Dependencies section and jasypt-maven-plugin plugin in Build section that will allow us to encrypt sensitive parameters in the application.properties file, ensuring secure usage within our application.

    Review the following dependencies added in the pom.xml file.

    POM file modified

    POM file modified

  3. Download the libraries and run the following commands.

    1. In your development environment, run the following command to access your project.

      cd tutorial
      

      Spring boot project folder

    2. Clean the project and remove all files generated by the previous build.

      mvn clean
      

      Maven Clean

    3. Purge (delete and optionally re-resolve) artifacts from the local maven repository.

      mvn dependency:purge-local-repository
      

    Maven Purge dependencies

  4. Already we have the dependencies and the pom.xml file configured in our project, we will proceed to check the SOAP XML file, as it represents the request from the client’s side, and the XSD file, which interprets the request on our Spring Boot project side.

    1. This SOAP XML file has two messages, with personal information and other kinds of attributes from two different clients that we will send per request as shown in the following image.

      Soap File

    2. Now on our Spring Boot project side, an XML schema is necessary to define a web service domain that Spring web service automatically exports as a WSDL, the following image shows the messages.xsd file defined for this tutorial.

      messages.xsd:

      XSD file

    3. Save the messages.xsd file in the resources folder of your Spring Boot project.

      XSD file in folder

  5. Build and install project files in a jar file. Run the following command and ensure that you are in your Spring Boot project folder.

    mvn install
    

    Note: Once the maven install command is executed, the target folder is automatically generated, and in the same way within it, the Java classes according to XSD file created previously and the project’s executable .jar file.

    Target Folder

  6. Now, we can add the required Java classes to our Spring Boot project.

    WebServiceConfig.java Class: This Java class was developed to create SOAP web service:

    • Sets up servlet to handle SOAP requests.
    • Generates a WSDL definition based on an XML schema.
    • Defines access endpoint of the SOAP web service.
    • Uses the messages.xsd schema file from the classpath.
    //Imports
    import org.springframework.boot.web.servlet.ServletRegistrationBean; //import the ServletRegistrationBean class
    import org.springframework.context.ApplicationContext; //import the ApplicationContext class
    import org.springframework.context.annotation.Bean; //import the Bean class
    import org.springframework.context.annotation.Configuration; //import the Configuration class
    import org.springframework.core.io.ClassPathResource; //import the ClassPathResource class
    import org.springframework.ws.config.annotation.EnableWs; //import the EnableWs class
    import org.springframework.ws.config.annotation.WsConfigurerAdapter; //import the WsConfigurerAdapter class
    import org.springframework.ws.transport.http.MessageDispatcherServlet; //import the MessageDispatcherServlet class
    import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition; //import the DefaultWsdl11Definition class
    import org.springframework.xml.xsd.SimpleXsdSchema; //import the SimpleXsdSchema class
    import org.springframework.xml.xsd.XsdSchema; //import the XsdSchema class
    
    //Configuration class for the Web Service configuration
    @EnableWs //Enable the Web Service
    @Configuration //Define the class as a Configuration class 
    
    public class WebServiceConfig extends WsConfigurerAdapter {
    
       //Create a ServletRegistrationBean object to register the MessageDispatcherServlet object with the application context
       @Bean
       public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
          MessageDispatcherServlet servlet = new MessageDispatcherServlet(); //Create a MessageDispatcherServlet object
          servlet.setApplicationContext(applicationContext); //Set the application context for the MessageDispatcherServlet object
          servlet.setTransformWsdlLocations(true); //Set the transformWsdlLocations property to true
          return new ServletRegistrationBean<>(servlet, "/ws/*"); //Return a new ServletRegistrationBean object with the MessageDispatcherServlet object and the URL pattern
       }
    
       //Create a DefaultWsdl11Definition object to define the WSDL
       @Bean(name = "messages")
       public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema messagesSchema) {
          DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition(); //Create a DefaultWsdl11Definition object
          wsdl11Definition.setPortTypeName("MessagesPort"); //Set the port type name
          wsdl11Definition.setLocationUri("/ws"); //Set the location URI
          wsdl11Definition.setTargetNamespace("http://tutorial_example.com/ns0"); //Set the target namespace
          wsdl11Definition.setSchema(messagesSchema); //Set the schema
          return wsdl11Definition; //Return the DefaultWsdl11Definition object
       }
    
       //Create a XsdSchema object to define the schema
       @Bean
       public XsdSchema messagesSchema() {
          return new SimpleXsdSchema(new ClassPathResource("messages.xsd")); //Return a new SimpleXsdSchema object with the messages.xsd file
       }
    }
    

    Note: If you want to test the web service, you can run the Spring Boot project in the same local development environment desktop and send HTTP request using curl, as follows:

    mvn spring-boot:run
    

    Run Spring boot project

    Run Spring boot project

    Once the project is running and the web service is up, run a local SOAP HTTP request using curl, as follows:

    curl --location 'http://localhost:8080/ws/'
    

    And you will get a response from the web service exposed in our Spring Boot project.

    SOAP Web Service

  7. Create a folder called model and in this folder we will add the following Java classes.

    Model Folder

    Note: These Java classes Car, House and User will retrieve the information based on the data in each SOAP message extracted from the HTTP SOAP request.

    • Car.java class: This Java class represents a Car object with their attributes linked to each user.

      ```
      //Imports
      import org.springframework.stereotype.Component; // For component scanning
      import org.springframework.context.annotation.Scope; // For defining bean scope
      
      @Component // Marks a class as a Spring-managed component
      @Scope("prototype") //A new instance is created every time the bean is requested
      
      public class Car {
      
         //Attributes
      
         private String brand; 
         private String color; 
         private String plate; 
      
         //"getter" and "setter" methods to get and set the information in each object
      
         public String getBrand(){
         return brand;
         }
      
         public void setBrand(String brand){
         this.brand = brand;
         }
      
         public String getColor(){
         return color;
         }
      
         public void setColor(String color){
         this.color = color;
         }
      
         public String getPlate(){
         return plate;
         }
      
         public void setPlate(String plate){
         this.plate = plate;
         }
      
      }
      ```
      
      • House.java class: This Java class represents a House object with their attributes linked to each user.

        //Imports
        import org.springframework.stereotype.Component; // For component scanning
        import org.springframework.context.annotation.Scope; // For defining bean scope
        
        @Component // Marks a class as a Spring-managed component
        @Scope("prototype") //A new instance is created every time the bean is requested
        
        public class House {
        
           //Attributes
        
           private int floors;
           private String location;
           private int rooms;
           private int bathrooms;
        
        
           //"getter" and "setter" methods to get and set the information in each object
        
           public int getFloors(){
              return floors;
           }
        
           public void setFloors(int floors){
              this.floors = floors;
           }
        
           public String getLocation(){
              return location;
           }
        
           public void setLocation(String location){
              this.location = location;
           }
        
           public int getRooms(){
              return rooms;
           }
        
           public void setRooms(int rooms){
              this.rooms = rooms;
           }
        
           public int getBathRooms(){
              return bathrooms;
           }
        
           public void setBathRooms(int bathrooms){
              this.bathrooms = bathrooms;
           }
        
        }
        
      • User.java class: This Java class represents a User object with their attributes, containing Car and House objects.

        //Imports
        import org.springframework.stereotype.Component; // For component scanning
        import org.springframework.context.annotation.Scope; // For defining bean scope
        
        @Component // Marks a class as a Spring-managed component
        @Scope("prototype") //A new instance is created every time the bean is requested
        
        
        public class User {
        
           //Attributes
        
           private String username; 
           private String userlastname;
           private int id;
           private String email;
           private String date;
           private String attribute;
           private Car car;
           private House house;
        
        
           //"getter" and "setter" methods to get and set the information in each object
        
           public String getUserName(){
           return username;
           }
        
           public void setUserName(String username){
           this.username = username;
           }
        
           public String getUserLastName(){
           return userlastname;
           }
        
           public void setUserLastName(String userlastname){
           this.userlastname = userlastname;
           }    
        
           public int getID(){
           return id;
           }
        
           public void setID(int id){
           this.id = id;
           }        
        
           public String getEmail(){
           return email;
           }
        
           public void setEmail(String email){
           this.email = email;
           }
        
           public String getDate(){
           return date;
           }
        
           public void setDate(String date){
           this.date = date;
           }
        
           public String getAttribute(){
           return attribute;
           }
        
           public void setAttribute(String attribute){
           this.attribute = attribute;
           }
        
           public Car getCar(){
           return car;
           }
        
           public void setCar(Car car){
           this.car = car;
           }
        
           public House getHouse(){
           return house;
           }
        
           public void setHouse(House house){
           this.house = house;
           }
        }
        
  8. Now we will configure the necessary parameters in our Spring Boot project to store the obtained data in the OCI ATP database. In the resources folder, you must find the application.properties file used to add parameters required by the application. It is automatically generated and is loaded by Spring Boot when the application starts.

    Note: It is really important to manage encryption and security methods that ensure sensitive information, such as passwords or relevant data, cannot be extracted or viewed by hackers. In this tutorial, we use the jasypt library configured in pom.xml file. For more information, see How to encrypt passwords in a Spring Boot project using Jasypt. Additionally in our Java classes on Spring Boot it is documented where to add the annotations and source code related with this library to decrypt the application.properties parameters.

    Add the appropriate parameters required by ATP database in the application.properties file, as shown in the following image.

    Application properties file

    Create the Spring Boot Java class to store each message in the database, it will be in database folder as shown in the following image.

    Database Folder

    • SoapObjectRepository.java: This Spring Boot Java class allows to insert in real-time transactional form, each message in ATP, using JDBC driver.

      //Java Classes USER, CAR, HOUSE Imports
      import com.oracle_springboot_tutorial.tutorial.model.*;
      
      //Spring Boot Imports
      import org.springframework.stereotype.Repository;
      import org.springframework.jdbc.core.JdbcTemplate;
      import org.springframework.beans.factory.annotation.Autowired;
      
      //Repository Class to save SOAP Messages in the Database
      @Repository
      public class SoapObjectRepository {
         private JdbcTemplate template;
         private User user;
      
         //Getters and Setters for JdbcTemplate template object
         public JdbcTemplate getTemplate(){
            return template;
         }
      
         //Autowired annotation to inject JdbcTemplate object into the template object
         @Autowired
         public void setTemplate(JdbcTemplate template){
            this.template = template;
         }
      
         //Method to save User SOAP Message in the Database
         public void saveUserSOAPMessage(User user){
            this.user = user;
            String sql = "INSERT INTO USERS (username, userlastname, id, email, dateuser, attributeuser) VALUES(?, ?, ?, ?, ?, ?)";
            template.update(sql, user.getUserName(), user.getUserLastName(), user.getID(), user.getEmail(), user.getDate(), user.getAttribute());
         }
      
         //Method to save Car SOAP Message in the Database
         public void saveCarSOAPMessage(Car car){
            String sql = "INSERT INTO CARS (userid, brand, color, plate) VALUES(?, ?, ?, ?)";
            template.update(sql, user.getID(), car.getBrand(), car.getColor(), car.getPlate());
         }
      
         //Method to save House SOAP Message in the Database
         public void saveHouseSOAPMessage(House house){
            String sql = "INSERT INTO HOUSES (userid, floors, locationhouse, rooms, bathrooms) VALUES(?, ?, ?, ?, ?)";
            template.update(sql, user.getID(), house.getFloors(), house.getLocation(), house.getRooms(), house.getBathRooms());
         }
      
      
      }
      

      Now, add the JSON software code, first creating the json_message folder and its Java Spring Boot class as shown in the following image.

      Json Folder

    • JsonBuilder.java: This Spring Boot Java class converts from SOAP XML format to JSON format.

      //Imports to be used for JSON
      import org.json.JSONArray;
      import org.json.JSONObject;
      
      //Imports to be used for the User class
      import com.oracle_springboot_tutorial.tutorial.model.*;
      
      
      //Imports to be used for the ArrayList class
      import java.util.ArrayList;
      
      
      public class JsonBuilder {
      
         //The buildJsonMessage method creates a JSON object from the ArrayList of User objects
         public JSONObject buildJsonMessage(ArrayList<User> usersMessageArray) {
      
            JSONObject rootJson = new JSONObject(); //Create a new JSON object called rootJson
            JSONObject messagesJson = new JSONObject(); //Create a new JSON object called messagesJson
            JSONArray messageArray = new JSONArray(); //Create a new JSON array called messageArray
      
      
            //Iterate through the ArrayList of User objects and create a JSON object for each User object in the ArrayList
            for (User user : usersMessageArray) {
                  JSONObject messageJson = new JSONObject();
                  messageJson.put("username", user.getUserName()); //Add the username of the user to the messageJson object
                  messageJson.put("userlastname", user.getUserLastName()); //Add the userlastname of the user to the messageJson object
                  messageJson.put("id", user.getID()); //Add the id of the user to the messageJson object
                  messageJson.put("email", user.getEmail()); //Add the email of the user to the messageJson object
                  messageJson.put("date", user.getDate()); //Add the date of the user to the messageJson object
                  messageJson.put("attribute", user.getAttribute()); //Add the attribute of the user to the messageJson object
      
      
                  //
                  JSONObject bodyJson = new JSONObject(); //Create a new JSON object called bodyJson
                  JSONObject envelopeJson = new JSONObject(); //Create a new JSON object called envelopeJson
      
                  //Switch statement to check the attribute of the User object
                  switch (user.getAttribute()) {
                     case "CAR":
                              Car car = user.getCar();
                              envelopeJson.put("brand", car.getBrand()); //Add the brand of the car to the envelopeJson object
                              envelopeJson.put("color", car.getColor()); //Add the color of the car to the envelopeJson object
                              envelopeJson.put("plate", car.getPlate()); //Add the plate of the car to the envelopeJson object                       
                        break;
                     case "HOUSE":
                              House house = user.getHouse();
                              envelopeJson.put("floors", house.getFloors()); //Add the floors of the house to the envelopeJson object
                              envelopeJson.put("location", house.getLocation()); //Add the location of the house to the envelopeJson object
                              envelopeJson.put("rooms", house.getRooms()); //Add the rooms of the house to the envelopeJson object
                              envelopeJson.put("bathrooms", house.getBathRooms()); //Add the bathrooms of the house to the envelopeJson object                     
                        break;
                     default:
                        System.out.println("Unknown subject: " + user.getAttribute());
                  }
      
      
                  bodyJson.put("envelope", envelopeJson); //Add the envelopeJson object to the bodyJson object
      
                  messageJson.put("body", bodyJson); //Add the bodyJson object to the messageJson object
      
               messageArray.put(messageJson); //Add the messageJson object to the messageArray array
            }
      
            messagesJson.put("message", messageArray); //Add the messageArray array to the messagesJson object
            rootJson.put("messages", messagesJson); //Add the messagesJson object to the rootJson object
      
            return rootJson;
         }
      }
      
  9. Now, we can send the messages in JSON format to OCI Queue. Create the oci_queue folder and its Java Spring Boot class as shown in the following image.

    Queue Folder

    Note: In OCIQueue.java class, we will need to define the access from OKE to OCI Queue. For this tutorial, we will use Workloads Access to grant access to OCI resources, without having to handle sensitive information such as user, passwords, OCIDs associated with your tenancy. For more information, see Granting Workloads Access to OCI Resources.

    Before to start developing OCIQueue.java class, we will configure workloads access in our tenancy. First, we need to create a namespace to associate with our Oracle GraalVM based Java application. Ensure you are in admin host.

    kubectl create ns-tutorial
    

    Then, create a Kubernetes service account for the application.

    kubectl create serviceaccount tutorialserviceaccount --namespace ns-tutorial
    

    Now, define an OCI IAM policy to allow the workload to access the necessary OCI resources. In this tutorial, OCI Queue.

    Go to the OCI Console, navigate to Identity & Security, Policies and click Create policy. Enter the following information and click Create.

    • Name: Enter preferred policy name.
    • Description: Enter Access from oke to oci queue.
    • Policy Builder:

      Allow any-user to use queues in compartment id ocid1.compartment.oc1..xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx where all {request.principal.type = 'workload', request.principal.namespace = 'ns-tutorial', request.principal.service_account = 'tutorialserviceaccount', request.principal.cluster_id = 'ocid1.cluster.oc1.sa-saopaulo-1.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'}
      

    Workloads access Policy

    Once you configure the workload access policy, namespace and service account, we can continue.

    In the application.properties file, add the queue parameters required to connect and manage the specific OCI Queue created in Task 5.

    OCIID Settings

    • OCIQueue.java: This Spring Boot Java class allows to access and put the messages in OCI Queue.

      //Imports
      import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider; //Import OkeWorkloadIdentityAuthenticationDetailsProvider to enable access and use OCI Workload Identity
      import com.oracle.bmc.queue.QueueClient; //Import QueueClient to have access and manage of OCI Queue
      import com.oracle.bmc.queue.model.PutMessagesDetails; //Import PutMessagesDetails to send messages to the OCI Queue
      import com.oracle.bmc.queue.model.PutMessagesDetailsEntry; //Import PutMessagesDetailsEntry to send messages to the OCI Queue
      import com.oracle.bmc.queue.requests.PutMessagesRequest; //Import PutMessagesRequest to send messages to the OCI Queue
      
      //Imports for the ArrayList and List
      import java.util.ArrayList;
      import java.util.List;
      
      public class OCIQueue {
         //Set the required parameters to access to OCI and the Queue
      
         //Variables
         private String queueId; 
         private String endPoint;
         private String region;
      
         //Constructor to initialize the OCI Queue object with the required parameters
         public OCIQueue(String queueId, String endPoint, String region){
            this.queueId = queueId;
            this.endPoint = endPoint;
            this.region = region;   
         }
      
      
         //The sendMessages method sends a message to the OCI Queue
         public void sendMessages(String jsonMessage){
            try{    
      
                  //Create an OkeWorkloadIdentityAuthenticationDetailsProvider object to authenticate the OCI Queue
                  OkeWorkloadIdentityAuthenticationDetailsProvider provider = new OkeWorkloadIdentityAuthenticationDetailsProvider.OkeWorkloadIdentityAuthenticationDetailsProviderBuilder().build();
      
                  //Create a QueueClient object to send the message to the OCI Queue
                  QueueClient queueClient = QueueClient.builder().build(provider);
                  queueClient.setRegion(region);
                  queueClient.setEndpoint(endPoint);
      
      
      
                  //Create a PutMessagesDetailsEntry object to send the message
                  PutMessagesDetailsEntry message = PutMessagesDetailsEntry.builder()
                     .content(jsonMessage)
                     .build();
      
                  //Create a List of PutMessagesDetailsEntry objects to send the message
                  List<PutMessagesDetailsEntry> messages = new ArrayList<>();
                  messages.add(message);
      
                  //Create a PutMessagesDetails object to send the message
                  PutMessagesDetails putMessagesDetails = PutMessagesDetails.builder()
                     .messages(messages)
                     .build();
      
      
                  //  Create a PutMessagesRequest object to send the message
                  PutMessagesRequest putMessagesRequest = PutMessagesRequest.builder()
                     .queueId(queueId)
                     .putMessagesDetails(putMessagesDetails)
                     .build();
      
                  // Send the request and get the response
                  queueClient.putMessages(putMessagesRequest);
      
            }catch(Exception e){
                  System.out.println("Exception sending message to OCI Queue: "+e);
            }
         }
      }
      
  10. Once you have the Spring Boot Java classes for database, JSON and OCI Queue, we can proceed with MessagesEndpoint.java class.

    For this, we will create a folder called endpoint and its Spring Boot Java class.

    Endpoint Folder

    Note: In MessagesEndpoint.java, we need to import some autogenerated classes. To do this, add the following source in the Configuration section of the pom.xml file:

    <configuration>
       <sources>
       <source>${project.build.directory}/generated-sources</source>
       </sources>
    </configuration>
    

    The pom.xml file should look like.

    Generated Sources path

    • MessagesEndpoint.java: The purpose of this Spring Boot Java class is to extract the SOAP HTTP request and map its values to the User, Car, and House Java objects for each message. It then stores the extracted data in an ATP database for each SOAP XML transaction, converts the data from XML to JSON format, and places the messages in an OCI Queue. These messages can later be retrieved and deleted from the queue by consumers.

      //Imports
      import com.oracle_springboot_tutorial.tutorial.model.*; //Import all the classes from the model package
      import com.oracle_springboot_tutorial.tutorial.oci_queue.OCIQueue; //Import the OCIQueue class from the oci_queue package
      
      
      import com.tutorial_example.ns0.Messages;//Import the Messages class from the tutorial_example.ns0 package (Auto generated Java Classes from the WSDL)
      import com.tutorial_example.ns0.MessageType; //Import the MessageType class from the tutorial_example.ns0 package (Auto generated Java Classes from the WSDL)
      
      //Import the ArrayList class from the java.util package
      import java.util.ArrayList;
      
      //Spring Boot imports to be used for the SOAP Web Service
      import org.springframework.beans.factory.annotation.Autowired; //Import the @Autowired annotation to inject the SoapObjectRepository object
      import org.springframework.beans.factory.annotation.Value; //Import the @Value annotation to inject the values from the application.properties file
      import org.springframework.stereotype.Component; //Import the @Component annotation to register the class with Spring
      
      //Spring Boot imports 
      import org.springframework.ws.server.endpoint.annotation.Endpoint; //Import the @Endpoint annotation to register the class with Spring WS
      import org.springframework.ws.server.endpoint.annotation.PayloadRoot; //Import the @PayloadRoot annotation to specify the namespace URI and local part of the request payload
      import org.springframework.ws.server.endpoint.annotation.RequestPayload; //Import the @RequestPayload annotation to map the request payload to the method parameter
      import org.springframework.ws.server.endpoint.annotation.ResponsePayload; //Import the @ResponsePayload annotation to map the returned value to the response payload
      
      //Imports to be used storing SOAP information in the database 
      import com.oracle_springboot_tutorial.tutorial.database.SoapObjectRepository; //Import the SoapObjectRepository class from the database package
      
      //Imports to be used for JSON
      import com.oracle_springboot_tutorial.tutorial.json_message.JsonBuilder; //Import the JsonBuilder class from the json_message package 
      import org.json.JSONObject; //Import the JSONObject class from the org.json package
      
      
      //The @Endpoint annotation registers the class with Spring WS.
      //The @Component annotation registers the class with Spring to be used as a Spring Bean.
      @Endpoint
      @Component
      public class MessagesEndpoint {
      
         //Inject not encrypted and decrypted values using jasypt library from the application.properties file
         @Value("${oci.queue.queueId}")
         private String queueId;
         @Value("${oci.queue.endPoint}")
         private String endPoint;
         @Value("${oci.queue.region}")   
         private String region;
         @Value("${spring.datasource.password}")
         private String datasourcePassword;
      
         //The @Autowired loads JDBC template in SoapObjectRepository.
         @Autowired
         private SoapObjectRepository soapObjectRepository = new SoapObjectRepository();
         //Create a new instance of the JsonBuilder class
         JsonBuilder jsonBuilder = new JsonBuilder();
      
      
         //The namespace URI
         private static final String NAMESPACE_URI = "http://tutorial_example.com/ns0";
      
         //The handleMessagesRequest method is annotated with @PayloadRoot, which means that it is invoked when a request with the specified namespace URI and local part is received.
         @PayloadRoot(namespace = NAMESPACE_URI, localPart = "messages")
         //The @ResponsePayload annotation makes Spring WS map the returned value to the response payload.
         @ResponsePayload
         //The handleMessagesRequest method processes the request and sends the message to the OCI Queue.
         public void handleMessagesRequest(@RequestPayload Messages request) {
            OCIQueue ociQueue = new OCIQueue(queueId, endPoint, region);
      
            //Create an ArrayList to store the users
            ArrayList<User> usersMessageArray = new ArrayList<User>();
      
            //Iterate over the messages, extracting the SOAP Messages and storing in the Java Objects (Car, House, User) 
            for (MessageType message : request.getMessage()) {
      
                  User user = new User();
                  user.setUserName(message.getUsername());
                  user.setUserLastName(message.getUserlastname());
                  user.setID(message.getId());
                  user.setEmail(message.getEmail());
                  user.setDate(message.getDate());
                  user.setAttribute(message.getAttribute());
      
                  //Insert User in Oracle ATP
                  soapObjectRepository.saveUserSOAPMessage(user);
      
                  //Process the attributes Car or House depending of the kind of User
                  processMessage(user, message);
                  //Add the user to the ArrayList
                  usersMessageArray.add(user);
            }
      
      
            //Convert to JSON format
            JSONObject jsonObject = jsonBuilder.buildJsonMessage(usersMessageArray);
      
            //Send the JSON message to OCI Queue
            ociQueue.sendMessages(jsonObject.toString());
      
         }
      
         //The processMessage method processes the message based on the user's attribute.
         private void processMessage(User user, MessageType message) {
            String subject = user.getAttribute();
            switch (subject) {
                  case "CAR":
                     handleCAR(user, message);
                     break;
                  case "HOUSE":
                     handleHouse(user, message);
                     break;
                  default:
                     System.out.println("Unknown subject: " + subject);
            }
         }
      
         //The handleCAR method processes the CAR message.
         private void handleCAR(User user, MessageType message) {
            Car car = new Car();
            car.setBrand(message.getBody().getEnvelope().getBrand());
            car.setColor(message.getBody().getEnvelope().getColor());
            car.setPlate(message.getBody().getEnvelope().getPlate());
      
            user.setCar(car);
            //Insert Car in Oracle ATP
            soapObjectRepository.saveCarSOAPMessage(user.getCar());
      
         }
      
         //The handleHouse method processes the HOUSE message.
         private void handleHouse(User user, MessageType message) {
            House house = new House();
            house.setFloors(message.getBody().getEnvelope().getFloors());
            house.setLocation(message.getBody().getEnvelope().getLocation());
            house.setRooms(message.getBody().getEnvelope().getRooms());
            house.setBathRooms(message.getBody().getEnvelope().getBathrooms());
      
            user.setHouse(house);
      
            //Insert Houses in Oracle ATP
            soapObjectRepository.saveHouseSOAPMessage(user.getHouse());
      
         }
      }
      
  11. Now that we have finished the entire construction of the Spring Boot project, we will create the Dockerfile in project folder.

  1. Run the following command to build and push the project image in the local Docker repository.

    docker build . -t springbootapp:latest
    

    Build Docker image

  2. Run the following command to verify the image in the local Docker repository.

    docker images
    

    Docker image local Repo

  3. We can tag the Spring Boot application image with complete path of your OCI Container Registry Classic repository.

    docker tag springbootapp:latest gru.ocir.io/xxxxxxxxxx/springboot/tutorialapp:latest
    
  4. Run the following command to verify in the local Docker repository.

    docker images
    

    Tag OKE Application

  5. Run the following command to push the image to OCI Container Registry Classic.

    docker push gru.ocir.io/xxxxxxxxxx/springboot/tutorialapp:latest
    

    Push OKE Application

  6. To review the OKE image application in OCI Container Registry Classic, go to Developer Services, Containers & Artifacts and click Container Registry.

    Image in OKE Containe Registry

    Once the image is in the OCI Container Registry Classic, we can go to our development environment and deploy this image in OKE. For this tutorial, run the following command to create the required configurations.

    Note: As the namespace and service account were configured before, it requires the secret.

    1. Run the following command to access the project folder.

      cd tutorial/
      
    2. Run the following command to create secret for OKE.

      kubectl create secret -n ns-tutorial generic ocir --from-file=.dockerconfigjson=../.docker/config.json --type=kubernetes.io/dockerconfigjson
      

      Create secrete oke

  7. We already have OKE environment ready so deploy the application image from OCI Container Registry Classic to OKE.

    Note: To deploy the application image, it is necessary to have a manifest file. In this tutorial, the following yaml file is the manifest file, it is used to deploy the application and create the ingress service represented in an OCI Load Balancer that is listening using 80 port.

    • springboot_application.yaml:

      apiVersion: apps/v1
      kind: Deployment
      metadata:
      name: soap-oci-queue-app
      namespace: ns-tutorial 
      labels:
         app: soap-oci-queue-app
      spec:
      replicas: 6
      selector:
         matchLabels:
            app: soap-oci-queue-app
      template:
         metadata:
            labels:
            app: soap-oci-queue-app
         spec:
            serviceAccountName: tutorialserviceaccount
            automountServiceAccountToken: true      
            containers:
            - name: soap-oci-queue-app
            image: gru.ocir.io/xxxxxxxxxxxx/springboot/tutorialapp:latest
            ports:
            - containerPort: 8080
            imagePullSecrets:
            - name: ocir-docker-config
      ---
      
      apiVersion: v1
      kind: Service
      metadata:
      name: svc-dev-app 
      namespace: ns-tutorial 
      spec:
      selector:
         app: soap-oci-queue-app
      ports:
         - port: 80
            targetPort: 8080
      type: LoadBalancer
      
  8. Run the kubectl command in the folder where you saved the manifest file.

    kubectl apply -f springboot_application.yaml
    

    Now, the application is deployed and the ingress load balancer service is created in OKE.

    Apply Manifest in OKE

  9. To validate the pod and service created in OKE, run the following command.

    kubectl get pods -A
    

    OKE PODS

    kubectl get svc -A
    

    OKE Services

    Note: Download Spring Boot Oracle GraalVM based Java application project from here: tutorial.zip.

Task 7: Test Spring Boot Oracle Graal VM Application using JMeter

For more information about the JMeter installation, see Getting Started with JMeter.

Once JMeter is installed and configured, you can send HTTP POST SOAP requests. For example, set the number of threads to 2 to represent 2 simultaneous users or applications, and set the Loop Count to 3000, meaning each user or application will send 3000 requests, for a total of 6000 SOAP requests.

SOAP Messages

In JMeter set our OCI Load Balancer IP, path configured in Spring Boot project and SOAP XML in the body.

HTTP SOAP Request

Run JMeter with 6000 SOAP transactions, and verify.

Note: As we are simulating the SOAP messaging of client applications, for each SOAP HTTP request the information is the same as seen in the SOAP XML file shown above, and does not change, but in a real customer environment the information will surely vary.

Run the following statement:

  1. To see total data stored in ATP.

    Oracle ATP

  2. To see details of data stored in each table in ATP.

    • CARS:

      CARS

    • HOUSES:

      HOUSES

    • USERS:

      USERS

  3. To see the total OCI Queue requests stored in OCI Queue.

    Total OCI Queue requests

  4. To see message details in OCI Queue in JSON format.

    OCI Queue messages Detail

Acknowledgments

More Learning Resources

Explore other labs on docs.oracle.com/learn or access more free learning content on the Oracle Learning YouTube channel. Additionally, visit education.oracle.com/learning-explorer to become an Oracle Learning Explorer.

For product documentation, visit Oracle Help Center.