By kswaughs | Thursday, September 19, 2024

Spring Boot ShedLock Example

Spring ShedLock is a distributed lock for scheduled tasks. It ensures that only one instance of a scheduled task runs at the same time in a distributed environment, such as a cluster of microservices. This is particularly useful for tasks that should not be executed concurrently, like data processing or cleanup jobs.

How ShedLock Works

Lock Provider: A lock provider is configured to manage the locks. This could be a database, Redis, etc.

Annotations: Tasks are annotated with @SchedulerLock to specify lock names and durations.

Execution: When a task is scheduled to run, ShedLock checks if a lock is available. If it is, the task runs and the lock is held for the specified duration.

Release: After the task completes or the lock duration expires, the lock is released, allowing other instances to acquire it.


Step 1. Add Dependencies


pom.xml
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>4.27.0</version>
</dependency>
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>4.27.0</version>
</dependency>

Step 2. Create Configuration Class

Create a configuration class to set up ShedLock with a JDBC lock provider.

ShedLockConfig
package com.kswaughs.batch.config;

import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

@Configuration
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class ShedLockConfig {

    @Bean
    public LockProvider lockProvider(JdbcTemplate jdbcTemplate) {
        return new JdbcTemplateLockProvider(
            JdbcTemplateLockProvider.Configuration.builder()
                .withJdbcTemplate(jdbcTemplate)
                .usingDbTime() // Works with PostgreSQL, MySQL, MariaDB, MS SQL, Oracle, DB2
                .build()
        );
    }
}

Step 3: Create Scheduled Task

Create a scheduled task that uses ShedLock to ensure only one instance of the task runs at a time.

ScheduledTasks
package com.kswaughs.batch.job;

import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledTasks {

    @Scheduled(cron = "0 0/5 * * * ?")
    @SchedulerLock(name = "TaskName", lockAtMostFor = "PT10M", lockAtLeastFor = "PT5M")
    public void performTask() {
        // Task implementation
        System.out.println("Scheduled task is running...");
    }
}

Step 4: Enable Scheduling

Ensure that scheduling is enabled in your Spring Boot application by adding @EnableScheduling to your main application class.

Main class
package com.kswaughs.batch;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Step 5: Create Database Table - ShedLock

The ShedLock library does not create the table automatically. You need to create the table manually. Here is the SQL script to create the ShedLock table.

Database Table
CREATE TABLE shedlock(
    name VARCHAR(64) NOT NULL,
    lock_until TIMESTAMP(3) NOT NULL,
    locked_at TIMESTAMP(3) NOT NULL,
    locked_by VARCHAR(255) NOT NULL,
    PRIMARY KEY (name)
);

Key Features
Distributed LockingEnsures that only one instance of a task runs at a time across multiple nodes.
Multiple Lock ProvidersSupports various lock providers like JDBC, MongoDB, Redis, ZooKeeper, etc.
AnnotationsUses annotations like @SchedulerLock to easily lock scheduled tasks.
ConfigurationCan be configured using Spring's configuration classes and properties.
IntegrationSeamlessly integrates with Spring's @Scheduled tasks.

Benefits
Prevents Duplicate ExecutionEnsures that tasks are not executed concurrently in a distributed setup.
Easy to UseSimple annotations and configuration make it easy to integrate into existing Spring applications.
FlexibleSupports various backends for lock storage, making it adaptable to different environments.

Recommend this on


By kswaughs | Thursday, September 5, 2024

What is Generative AI

Generative AI refers to a class of artificial intelligence models that can generate new content, such as text, images, music, or even code, based on the patterns and data they have been trained on. These models use techniques from machine learning, particularly deep learning, to create outputs that are similar to the examples they were trained on.

Usefulness for Java Developers

1. Code Generation

Boilerplate Code: Generative AI can help generate repetitive boilerplate code, saving time and reducing errors.

Code Snippets: It can provide code snippets for common tasks, such as database connections, REST API endpoints and more.

2. Code Completion

Intelligent Suggestions: Tools like GitHub Copilot can offer intelligent code completion suggestions, helping developers write code faster and with fewer errors.

3. Documentation

Auto-Generated Documentation: Generative AI can help create documentation for code, including comments, README files, and API documentation.

4. Bug Fixing

Error Detection and Correction: AI can assist in identifying bugs and suggesting possible fixes, improving code quality and reducing debugging time.

5. Learning and Training

Educational Tools: Generative AI can be used to create educational content, tutorials, and examples to help developers learn new concepts and technologies.

6. Testing

Test Case Generation: AI can generate test cases based on the code, ensuring better test coverage and more robust applications.


Example Tools

GitHub Copilot: An AI-powered code completion tool that integrates with IDEs like IntelliJ IDEA, providing real-time code suggestions.

TabNine: An AI code completion tool that supports multiple programming languages, including Java.

Kite: An AI-powered coding assistant that offers code completions and documentation.

Generative AI can significantly enhance productivity, reduce development time, and improve code quality for Java developers.

Recommend this on


By kswaughs | Thursday, August 22, 2024

SOLID Principles Java Example

1. Single Responsibility Principle (SRP)

2. Open/Closed Principle (OCP)

3. Liskov Substitution Principle (LSP)

4. Interface Segregation Principle (ISP)

5. Dependency Inversion Principle (DIP)

1. Single Responsibility Principle (SRP)

A class should have only one reason to change, meaning it should have only one job or responsibility.

Example

// Class responsible for handling user data
public class User {
    private String name;
    private String email;

    // Getters and setters
}

// Class responsible for user persistence
public class UserRepository {
    public void save(User user) {
        // Code to save user to database
    }
}

2. Open/Closed Principle (OCP)

It states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means that the behavior of a module can be extended without modifying its source code.

Key Points
1. Open for Extension: You should be able to add new functionality to the module.
2. Closed for Modification: You should not change the existing code of the module.

Consider a scenario where you have a `Shape` class and you want to calculate the area of different shapes like `Circle` and `Rectangle`. Initially, you might have a single class with a method that handles all shapes, which violates the Open/Closed Principle.

Bad Example (Violating OCP)

public class Shape {
    public enum Type { CIRCLE, RECTANGLE }

    private Type type;
    private double radius;
    private double width;
    private double height;

    public Shape(Type type, double radius, double width, double height) {
        this.type = type;
        this.radius = radius;
        this.width = width;
        this.height = height;
    }

    public double calculateArea() {
        switch (type) {
            case CIRCLE:
                return Math.PI * radius * radius;
            case RECTANGLE:
                return width * height;
            default:
                throw new UnsupportedOperationException("Shape type not supported");
        }
    }
}

In this example, if you want to add a new shape, you need to modify the `Shape` class, which violates the Open/Closed Principle.

Good Example (Adhering to OCP):

  • Define an abstract `Shape` class with an abstract `calculateArea` method.
  • Create concrete classes for each shape that extend the `Shape` class and implement the `calculateArea` method.
Good Example (Adhering to OCP)

// Abstract Class:

public abstract class Shape {
    public abstract double calculateArea();
}

// Concrete Classes:

public class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

public class Rectangle extends Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double calculateArea() {
        return width * height;
    }
}

// Usage:

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle(5);
        Shape rectangle = new Rectangle(4, 6);

        System.out.println("Circle Area: " + circle.calculateArea());
        System.out.println("Rectangle Area: " + rectangle.calculateArea());
    }
}

In this refactored example, the `Shape` class is open for extension (you can add new shapes by creating new subclasses) but closed for modification (you don't need to change the existing `Shape` class to add new shapes).

3. Liskov Substitution Principle (LSP)

It states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. In other words, if class `S` is a subclass of class `T`, then objects of type `T` should be replaceable with objects of type `S` without altering the desirable properties of the program (correctness, task performed, etc.).

Key Points
1. Subtypes must be substitutable for their base types: Derived classes must be substitutable for their base classes.
2. Behavioral compatibility: Subtypes must behave in a way that does not violate the expectations established by the base type.

Consider a scenario where you have a base class `Bird` and a subclass `Ostrich`. According to LSP, the `Ostrich` class should be able to replace the `Bird` class without causing issues.

Bad Example (Violating LSP)
// Base Class:

public class Bird {
    public void fly() {
        System.out.println("Bird is flying");
    }
}


// Subclass Violating LSP:

public class Ostrich extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("Ostrich can't fly");
    }
}

In this example, the `Ostrich` class violates the Liskov Substitution Principle because it changes the expected behavior of the `fly` method. To adhere to LSP, we should design our classes in a way that does not violate the expectations of the base class.

Good Example (Adhering to LSP)

//Base Class:

public abstract class Bird {
    public abstract void move();
}

// Subclass:

public class FlyingBird extends Bird {
    @Override
    public void move() {
        System.out.println("Bird is flying");
    }
}

public class Ostrich extends Bird {
    @Override
    public void move() {
        System.out.println("Ostrich is running");
    }
}

// Usage:

public class BirdWatcher {
    public void watchBird(Bird bird) {
        bird.move();
    }

    public static void main(String[] args) {
        BirdWatcher watcher = new BirdWatcher();
        Bird flyingBird = new FlyingBird();
        Bird ostrich = new Ostrich();

        watcher.watchBird(flyingBird); // Output: Bird is flying
        watcher.watchBird(ostrich);    // Output: Ostrich is running
    }
}

In this refactored example, both `FlyingBird` and `Ostrich` adhere to the Liskov Substitution Principle by providing their own implementation of the `move` method, which does not violate the expectations of the `Bird` class.

4. Interface Segregation Principle (ISP)

It states that no client should be forced to depend on methods it does not use. This means that larger interfaces should be split into smaller, more specific ones so that clients only need to know about the methods that are of interest to them.

Key Points
1. Clients should not be forced to implement interfaces they do not use: This avoids "fat" interfaces.
2. Interfaces should be client-specific: Each interface should be tailored to the specific needs of a client.

Consider a scenario where you have an interface `Worker` that has methods for different types of workers.

Bad Example (Violating ISP)

public interface Worker {
    void work();
    void eat();
}

public class HumanWorker implements Worker {

    @Override
    public void work() {
        System.out.println("Human is working");
    }

    @Override
    public void eat() {
        System.out.println("Human is eating");
    }
}

public class RobotWorker implements Worker {

    @Override
    public void work() {
        System.out.println("Robot is working");
    }

    @Override
    public void eat() {
        // Robot does not eat, but forced to implement this method
        throw new UnsupportedOperationException("Robot does not eat");
    }
}

In this example, the 'RobotWorker' class is forced to implement the `eat` method, which it does not need, violating the Interface Segregation Principle.

Good Example (Adhering to ISP)
public interface Workable {
    void work();
}

public interface Eatable {
    void eat();
}

public class HumanWorker implements Workable, Eatable {
    @Override
    public void work() {
        System.out.println("Human is working");
    }

    @Override
    public void eat() {
        System.out.println("Human is eating");
    }
}

public class RobotWorker implements Workable {
    @Override
    public void work() {
        System.out.println("Robot is working");
    }
}

In this refactored example, the 'Worker interface is split into 'Workable' and 'Eatable' interfaces. Now, 'RobotWorker' only implements the 'Workable' interface, adhering to the Interface Segregation Principle.

5. Dependency Inversion Principle (DIP)

It states that high-level modules should not depend on low-level modules. Both should depend on abstractions. Additionally, abstractions should not depend on details. Details should depend on abstractions.

Consider a scenario where you have a `Light` class and a `Switch` class. The `Switch` class directly depends on the `Light` class, which violates the Dependency Inversion Principle.

Bad Example (Violating DIP)

public class Light {
    public void turnOn() {
        System.out.println("Light is turned on");
    }

    public void turnOff() {
        System.out.println("Light is turned off");
    }
}

public class Switch {
    private Light light;

    public Switch(Light light) {
        this.light = light;
    }

    public void operate(String command) {
        if (command.equalsIgnoreCase("ON")) {
            light.turnOn();
        } else if (command.equalsIgnoreCase("OFF")) {
            light.turnOff();
        }
    }
}

In this example, the `Switch` class directly depends on the `Light` class, which is a low-level module.

1. Define an abstraction for the `Switchable` interface. 2. Implement the `Switchable` interface in the `Light` class. 3. Modify the `Switch` class to depend on the `Switchable` interface.

Good Example (Adhering to DIP)

// Abstraction
public interface Switchable {
    void turnOn();
    void turnOff();
}

//Low-level Module
public class Light implements Switchable {

    @Override
    public void turnOn() {
        System.out.println("Light is turned on");
    }

    @Override
    public void turnOff() {
        System.out.println("Light is turned off");
    }
}

//High-level Module
public class Switch {
    private Switchable device;

    public Switch(Switchable device) {
        this.device = device;
    }

    public void operate(String command) {
        if (command.equalsIgnoreCase("ON")) {
            device.turnOn();
        } else if (command.equalsIgnoreCase("OFF")) {
            device.turnOff();
        }
    }
}

In this refactored example, the `Switch` class depends on the `Switchable` interface, which is an abstraction. The `Light` class implements the `Switchable` interface. This way, the high-level module (`Switch`) does not depend on the low-level module (`Light`), adhering to the Dependency Inversion Principle.

Recommend this on


By kswaughs | Monday, June 22, 2020

Java CompleteableFuture Example

CompleteableFuture is an extension to Java's Future API that allows us to write a non-blocking code.

It runs a task on a separate thread and notifies the main thread about its completion or failure.

ForkJoinPool.commonPool() is the default thread pool that runs the task.

CompleteableFutureExample.java
package com.kswaughs.examples;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.kswaughs.model.Book;

public class CompleteableFutureExample {

    private static Logger log = LoggerFactory.getLogger("CompleteableFutureExample");

    public static void main(String[] args) {

        CompletableFuture<Book> resultbook = CompletableFuture.supplyAsync(() -> {
            log.info("I am Taking 3 secs to return a book");
            // Sleep for 3 seconds..
            sleep(3000);
            return getBook();
        });

        resultbook.thenAccept(book -> {
            log.info("Book received: \n {}", book);
        });

        log.info("I am not waiting for the book object");
        log.info("I will continue with other work..");

        // To prevent this program from terminating prematurely
        ForkJoinPool.commonPool().awaitQuiescence(10, TimeUnit.SECONDS);
    }

    private static Book getBook() {

        Book book = new Book();

        book.setBookId(123);
        book.setAuthor("John Grisham");
        book.setTitle("A Painted House");

        log.info("Returning book object");

        return book;
    }

    private static void sleep(int waitTime) {
        try {
            Thread.sleep(waitTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Output

21:01:42.742 [main] INFO CompleteableFutureExample - I am not waiting for the book object
21:01:42.742 [ForkJoinPool.commonPool-worker-1] INFO CompleteableFutureExample - I am Taking 3 secs to return a book
21:01:42.744 [main] INFO CompleteableFutureExample - I will continue with other work..
21:01:45.745 [ForkJoinPool.commonPool-worker-1] INFO CompleteableFutureExample - Returning book object
21:01:45.745 [main] INFO CompleteableFutureExample - Book received: 
 Book [bookId=123, title=A Painted House, author=John Grisham]

Recommend this on


By kswaughs | Thursday, June 11, 2020

Spring WebFlux Mono Junit Test Example

This example shows how to write junit test for a method that returns Mono.

StepVerifier from io.projectreactor.reactor-test is used to test reactive components.

Below is the Service class that returns Mono.

BookService.java
package com.kswaughs.webflux;

import com.kswaughs.webflux.model.Book;

import reactor.core.publisher.Mono;

public class BookService {
    
    public Mono<Book> getBookById(int bookId) {
        
        if(bookId == 0) {
            return Mono.error(new Exception("Invalid BookId"));
        }
        
        if(bookId == 100) {
            
            return Mono.just(buildBook(bookId));
        }
        
        return Mono.empty();
    }
    
    private Book buildBook(int bookId) {
        
        Book book = new Book();
        
        book.setBookId(bookId);
        book.setAuthor("John Grisham");
        book.setTitle("A Painted House");

        return book;
    }
}


Below is the Junit test class to test Mono using StepVerifier

SpringWebFluxMonoExampleTest.java
package com.kswaughs.webflux;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

import reactor.test.StepVerifier;

public class SpringWebFluxMonoExampleTest {

    private BookService bookService = new BookService();

    @Test
    public void whenBookExists() {

        StepVerifier.create(bookService.getBookById(100))
            .assertNext(book -> {

                assertEquals(Integer.valueOf(100), book.getBookId());
                assertEquals("John Grisham", book.getAuthor());
                assertEquals("A Painted House", book.getTitle());
                
        }).verifyComplete();
    }
    
    @Test
    public void whenBookNotExists() {

        StepVerifier.create(bookService.getBookById(56))
            .verifyComplete();
    }
    
    @Test
    public void whenBookIdIsInvalid() {

        StepVerifier.create(bookService.getBookById(0))
        
          /** case 1: To validate only Exception className  **/
            //.expectError(Exception.class)
        
          /** case 2: To validate only exception message **/    
            //.expectErrorMessage("Invalid BookId")
        
          /** case 3: To Validate complete exception object **/    
            .expectErrorSatisfies(thr -> {
                assertTrue(thr instanceof Exception);
                assertEquals("Invalid BookId", thr.getMessage());
            })
            .verify();
    }
}

pom.xml
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <scope>test</scope>
</dependency>

Useful Links

Spring WebFlux Junit Test Example

Recommend this on


By kswaughs | Tuesday, June 9, 2020

Spring WebFlux Junit Test Example

This example shows how to write junit test for a method that returns Flux of Order objects.

StepVerifier from io.projectreactor.reactor-test is used to test reactive components.

Below is the Service class that returns Flux of three Order objects.

OrderService.java
package com.kswaughs.webflux;

import java.util.ArrayList;
import java.util.List;

import com.kswaughs.webflux.model.Order;

import reactor.core.publisher.Flux;

public class OrderService {
    
   public Flux<Order> getOrders() {
       
       List<Order> orders = new ArrayList<>();
       
       orders.add(buildOrder(345, 30, "DELIVERED"));
       orders.add(buildOrder(654, 50, "IN-TRANSIT"));
       orders.add(buildOrder(999, 12, "DELIVERED"));
       
       // return flux of orders..
       return Flux.fromIterable(orders);
   }

   private Order buildOrder(int orderId, double amount, String status) {
       
       Order order = new Order();
       order.setOrderId(orderId);
       order.setAmount(amount);
       order.setStatus(status);
       
       return order;
   }
}

Below is the Junit test class to test Flux of objects using StepVerifier

SpringWebFluxExampleTest.java
package com.kswaughs.webflux;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

import com.kswaughs.webflux.model.Order;

import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;

public class SpringWebFluxExampleTest {
    
    private OrderService orderService = new OrderService();

    @Test
    public void testGetOrders() {
        
        Flux<Order> orders = orderService.getOrders();
        
        StepVerifier.create(orders)
            // Above method is returning three orders.
            // we are asserting on first order only.
            .assertNext(order -> {
            
                assertEquals(Integer.valueOf(345), Integer.valueOf(order.getOrderId()));
                assertEquals(Double.valueOf(30), Double.valueOf(order.getAmount()));
                assertEquals("DELIVERED", order.getStatus());
            })
            // verifying count of next orders.
            .expectNextCount(2)
            .verifyComplete();
    }
}

pom.xml
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <scope>test</scope>
</dependency>

Useful Links

Spring WebFlux Mono Junit Test Example

Recommend this on


By kswaughs | Monday, June 1, 2020

Spring JdbcTemplate ResultSetExtractor Junit test example

This example shows how to write junit to test spring ResultSetExtractor functionality while mocking JdbcTemplate with Mockito.

This also increases code coverage of ResultSetExtractor code.

Below is the DAO class that returns Map of employees with ResultSetExtractor using Lambdas

Spring JdbcTemplate ResultSetExtractor Example with Lambdas
package com.kswaughs.dao;

import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.kswaughs.beans.Employee;

@Repository
public class EmployeeDAO {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public Map<Integer, Employee> getEmployeeMap() {

        return jdbcTemplate.query("SELECT ID, NAME, STATUS FROM EMPLOYEE",

            (ResultSet rs) -> {

                Map<Integer, Employee> employeeMap = new HashMap<>();

                while (rs.next()) {

                    Employee employee = new Employee();

                    employee.setId(rs.getInt("ID"));
                    employee.setName(rs.getString("NAME"));
                    employee.setStatus(rs.getBoolean("STATUS"));

                    employeeMap.put(employee.getId(), employee);
                }

                return employeeMap;
        });
    }
}

Below is the Junit test class to test ResultSetExtractor code by mocking JdbcTemplate to return two rows.

Spring JdbcTemplate ResultSetExtractor Junit test example
package com.kswaughs.dao;

import static org.mockito.Mockito.when;

import java.sql.ResultSet;
import java.util.Map;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;

import com.kswaughs.beans.Employee;

@RunWith(MockitoJUnitRunner.class)
public class EmployeeDAOTest {

    @Mock
    private JdbcTemplate jdbcTemplate;

    @InjectMocks
    private EmployeeDAO employeeDAO;

    @SuppressWarnings("unchecked")
    @Test
    public void testGetEmployeeMap() {

        Mockito.when(jdbcTemplate.query(
            ArgumentMatchers.anyString(), ArgumentMatchers.any(ResultSetExtractor.class)))
            .thenAnswer((invocation) -> {

                ResultSetExtractor<Map<Integer, Employee>> resultSetExtractor = 
                    (ResultSetExtractor<Map<Integer, Employee>>) invocation.getArgument(1);
                
                ResultSet rs = Mockito.mock(ResultSet.class);
                
                // two times it returns true and third time returns false.
                when(rs.next()).thenReturn(true, true, false);

                // Mock ResultSet to return two rows.
                Mockito.when(rs.getInt(ArgumentMatchers.eq("ID")))
                    .thenReturn(506, 400);
                Mockito.when(rs.getString(ArgumentMatchers.eq("NAME")))
                    .thenReturn("Jim Carrey", "John Travolta");
                Mockito.when(rs.getBoolean(ArgumentMatchers.eq("STATUS")))
                    .thenReturn(true, false);

                return resultSetExtractor.extractData(rs);
        });

        Map<Integer, Employee> employeeMap = employeeDAO.getEmployeeMap();
        
        // Assert First Row
        assertFirstUser(employeeMap.get(506));

        // Assert Second Row
        assertSecondUser(employeeMap.get(400));
    }

    public void assertFirstUser(Employee employee) {
        Assert.assertEquals(Integer.valueOf(506), employee.getId());
        Assert.assertEquals("Jim Carrey", employee.getName());
        Assert.assertTrue(employee.isStatus());
    }
    
    public void assertSecondUser(Employee employee) {
        Assert.assertEquals(Integer.valueOf(400), employee.getId());
        Assert.assertEquals("John Travolta", employee.getName());
        Assert.assertFalse(employee.isStatus());
    }
}

Recommend this on


By kswaughs | Thursday, May 28, 2020

Spring JdbcTemplate RowMapper Junit Test Example

This example shows how to write junit to test Spring RowMapper functionality while mocking JdbcTemplate with Mockito.

This also increases code coverage of RowMapper code.

Below is the DAO class that returns list of users with RowMapper using Lambdas.

Spring JdbcTemplate RowMapper Example with Lambdas
package com.kswaughs.dao;

import java.sql.ResultSet;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.kswaughs.beans.User;

@Repository
public class UserDAO {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public List<User> getAllUsers() {

        return jdbcTemplate.query("SELECT ID, NAME, STATUS FROM USER", 
            
            (ResultSet rs, int rowNum) -> {

                User user = new User();

                user.setId(rs.getInt("ID"));
                user.setName(rs.getString("NAME"));
                user.setStatus(rs.getBoolean("STATUS"));

                return user;
            });
        }
    }

Below is the Junit test class to test RowMapper code by mocking JdbcTemplate to return two rows.

Spring JdbcTemplate RowMapper Junit test example
package com.kswaughs.dao;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import com.kswaughs.beans.User;

@RunWith(MockitoJUnitRunner.class)
public class UserDAOTest {

    @Mock
    private JdbcTemplate jdbcTemplate;

    @InjectMocks
    private UserDAO userDAO;

    @SuppressWarnings("unchecked")
    @Test
    public void testGetAllUsers() {

        Mockito.when(jdbcTemplate.query(
            ArgumentMatchers.anyString(), ArgumentMatchers.any(RowMapper.class)))
            .thenAnswer((invocation) -> {

                RowMapper<User> rowMapper = (RowMapper<User>) invocation.getArgument(1);
                ResultSet rs = Mockito.mock(ResultSet.class);

                // Mock ResultSet to return two rows.
                Mockito.when(rs.getInt(ArgumentMatchers.eq("ID")))
                    .thenReturn(506, 400);
                Mockito.when(rs.getString(ArgumentMatchers.eq("NAME")))
                    .thenReturn("Jim Carrey", "John Travolta");
                Mockito.when(rs.getBoolean(ArgumentMatchers.eq("STATUS")))
                    .thenReturn(true, false);

                List<User> users = new ArrayList<>();
                users.add(rowMapper.mapRow(rs, 0));
                users.add(rowMapper.mapRow(rs, 1));

                return users;
        });

        List<User> users = userDAO.getAllUsers();
        
        // Assert First Row
        assertFirstUser(users.get(0));

        // Assert Second Row
        assertSecondUser(users.get(1));
    }

    public void assertFirstUser(User user) {
        Assert.assertEquals(Integer.valueOf(506), user.getId());
        Assert.assertEquals("Jim Carrey", user.getName());
        Assert.assertTrue(user.isStatus());
    }
    
    public void assertSecondUser(User user) {
        Assert.assertEquals(Integer.valueOf(400), user.getId());
        Assert.assertEquals("John Travolta", user.getName());
        Assert.assertFalse(user.isStatus());
    }
}

Recommend this on


By kswaughs | Monday, May 18, 2020

Spring Webflux Subscriber Example

This post explains how to subscribe to the flux of events with and without lambdas.

Flux publisher code is explained in the below link, that publishes multiplication table of given number starting from 0 to 10.

Spring Webflux publisher example

Example 1: Spring Webflux Subscriber with Lambdas
package com.kswaughs.webflux;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import reactor.core.publisher.Flux;

public class SubscriberExampleWithLambdas {
    
    private static Logger log = LoggerFactory.getLogger("SubscriberExampleWithLambdas");

    public static void main(String[] args) {
        
        ProducerExample producer = new ProducerExample();
        
        Flux<String> flux = producer.produceTableOf(8);
    
        String name = "Netflix";
        
        flux.subscribe(
                
            data -> { // on Next
                log.info("{} -> data received : {}", name, data); 
                }, 
            error -> { // on Error
                log.info("{} -> error received : {}",name, error);
                }, 
            ()-> { // on Completion
                log.info("{} completed", name); 
                }, 
            subscription -> { // on Subscribe   
                log.info("{} Susbcription requested", name);
                subscription.request(15);
            });
        }
}

Output

08:41:24.242 [main] INFO SubscriberExampleWithLambdas - Netflix Susbcription requested
08:41:24.242 [main] INFO SubscriberExampleWithLambdas - Netflix -> data received : 8 x 0 = 0
08:41:24.242 [main] INFO SubscriberExampleWithLambdas - Netflix -> data received : 8 x 1 = 8
08:41:24.242 [main] INFO SubscriberExampleWithLambdas - Netflix -> data received : 8 x 2 = 16
08:41:24.242 [main] INFO SubscriberExampleWithLambdas - Netflix -> data received : 8 x 3 = 24
08:41:24.242 [main] INFO SubscriberExampleWithLambdas - Netflix -> data received : 8 x 4 = 32
08:41:24.242 [main] INFO SubscriberExampleWithLambdas - Netflix -> data received : 8 x 5 = 40
08:41:24.242 [main] INFO SubscriberExampleWithLambdas - Netflix -> data received : 8 x 6 = 48
08:41:24.242 [main] INFO SubscriberExampleWithLambdas - Netflix -> data received : 8 x 7 = 56
08:41:24.242 [main] INFO SubscriberExampleWithLambdas - Netflix -> data received : 8 x 8 = 64
08:41:24.242 [main] INFO SubscriberExampleWithLambdas - Netflix -> data received : 8 x 9 = 72
08:41:24.242 [main] INFO SubscriberExampleWithLambdas - Netflix -> data received : 8 x 10 = 80
08:41:24.242 [main] INFO SubscriberExampleWithLambdas - Netflix completed

Example 2: Spring Webflux Subscriber without using Lambdas
package com.kswaughs.webflux;

import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import reactor.core.publisher.Flux;

public class SubscriberExample {
    
    private static Logger log = LoggerFactory.getLogger("SubscriberExample");

    public static void main(String[] args) {
        
        ProducerExample producer = new ProducerExample();
        
        Flux<String> flux = producer.produceTableOf(10);
        
        flux.subscribe(
                
            new Subscriber<String>() {
                
                String name = "Amazon";

                @Override
                public void onComplete() {
                    log.info("{} completed", name);    
                }

                @Override
                public void onError(Throwable error) {
                    log.info("{} -> error received : {}",name, error);    }

                @Override
                public void onNext(String data) {
                    log.info("{} -> data received : {}", name, data);    
                }

                @Override
                public void onSubscribe(Subscription subscription) {
                    log.info("{} Susbcription requested", name);
                    subscription.request(7);
                }
            }
        );
    }
}

Output

08:51:50.152 [main] INFO SubscriberExample - Amazon Susbcription requested
08:51:50.152 [main] INFO SubscriberExample - Amazon -> data received : 10 x 0 = 0
08:51:50.152 [main] INFO SubscriberExample - Amazon -> data received : 10 x 1 = 10
08:51:50.152 [main] INFO SubscriberExample - Amazon -> data received : 10 x 2 = 20
08:51:50.152 [main] INFO SubscriberExample - Amazon -> data received : 10 x 3 = 30
08:51:50.152 [main] INFO SubscriberExample - Amazon -> data received : 10 x 4 = 40
08:51:50.152 [main] INFO SubscriberExample - Amazon -> data received : 10 x 5 = 50
08:51:50.152 [main] INFO SubscriberExample - Amazon -> data received : 10 x 6 = 60

In the second example, as subscription requested for 7 elements, flux producer stopped publishing after 7 events. As all events are not completed, so onComplete() method is not executed.

Recommend this on


By kswaughs | Tuesday, May 12, 2020

Spring Webflux Publisher Example

This post explains how to produce or generate flux of events programmatically.

In this example, we are publishing multiplication table of given number starting from 0 to 10 and subscriber is printing each event in console.

PublisherExample.java
package com.kswaughs.webflux;

import reactor.core.publisher.Flux;

public class PublisherExample {

    public Flux<String> produceTableOf(int num) {

        return Flux.generate(
            () -> 0, // Supplier of Initial State
            (state, sink) -> {

                if (state > 10) {
                    sink.complete();
                    return state;
                }

                String calc = num + " x " + state + " = " + num * state;
                sink.next(calc);
                return state + 1;
            }, 
            state -> {
                System.out.println("Final state:" + state);
            });
    }

    public static void main(String[] args) {

        PublisherExample example = new PublisherExample();

        System.out.println("*** Table of 5 ****");
        example.produceTableOf(5).subscribe(System.out::println);

        System.out.println("\n*** Table of 3 ****");
        example.produceTableOf(3).subscribe(System.out::println);
    }
}

Output

*** Table of 5 ****
5 x 0 = 0
5 x 1 = 5
5 x 2 = 10
5 x 3 = 15
5 x 4 = 20
5 x 5 = 25
5 x 6 = 30
5 x 7 = 35
5 x 8 = 40
5 x 9 = 45
5 x 10 = 50
Final state:11

*** Table of 3 ****
3 x 0 = 0
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
3 x 4 = 12
3 x 5 = 15
3 x 6 = 18
3 x 7 = 21
3 x 8 = 24
3 x 9 = 27
3 x 10 = 30
Final state:11

Recommend this on


By kswaughs | Tuesday, May 5, 2020

How to get previous or next item of list in Java using Streams

Basically Java Streams does not provide any in-built operators to get previous or next item while processing current item of the list.

But we can achieve this by using reduce operator properly as per our needs.

In this example, we have list of transactions and each transaction has 'id' and 'currentBalance' fields. Now we will populate 'previousBalance' field of each transaction from previous transaction object.

TransactionExample.java
package com.kswaughs.example;

import java.util.ArrayList;
import java.util.List;

import com.kswaughs.model.Transaction;

public class TransactionExample {

    public static void main(String[] args) {
        
        List<Transaction> items = new ArrayList<>();
        items.add(new Transaction(1, 100.0));
        items.add(new Transaction(2, 200.0));
        items.add(new Transaction(3, 300.0));
        items.add(new Transaction(4, 400.0));

        items.stream().reduce((a,b) -> {
            b.setPrviousBalance(a.getCurrentBalance());
            return b;
        });
        
        System.out.println(items);
    }
}

Model class

Transaction.java
package com.kswaughs.model;

public class Transaction {

    private int id;
    
    private double currentBalance;
    
    private double prviousBalance;
    
    public Transaction(int id, double currentBalance) {
        this.id = id;
        this.currentBalance = currentBalance;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getCurrentBalance() {
        return currentBalance;
    }

    public void setCurrentBalance(double currentBalance) {
        this.currentBalance = currentBalance;
    }

    public double getPrviousBalance() {
        return prviousBalance;
    }

    public void setPrviousBalance(double prviousBalance) {
        this.prviousBalance = prviousBalance;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Transaction [id=").append(id)
            .append(", currentBalance=").append(currentBalance)
            .append(", prviousBalance=").append(prviousBalance)
            .append("]\n");
        return builder.toString();
    }
}

Output

[Transaction [id=1, currentBalance=100.0, prviousBalance=0.0]
, Transaction [id=2, currentBalance=200.0, prviousBalance=100.0]
, Transaction [id=3, currentBalance=300.0, prviousBalance=200.0]
, Transaction [id=4, currentBalance=400.0, prviousBalance=300.0]
]

Recommend this on


By kswaughs | Thursday, November 30, 2017

Spring Boot Soap Web Service Client Example

This post explains how to consume a soap web service with Spring Boot using org.springframework.ws.client.core.WebServiceTemplate class.

In this example, we will call Book Store web service which is already explained in the following post.

Spring Boot Soap Web Service Example

Step 1: Maven setup

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>com.kswaughs.spring</groupId>
    <artifactId>spring-boot-soap-service-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-boot-soap-service-client</name>
    <description>Demo project for Spring Boot SOAP Web service Client</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <maven-jaxb2-plugin.version>0.13.2</maven-jaxb2-plugin.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.jvnet.jaxb2.maven2</groupId>
                <artifactId>maven-jaxb2-plugin</artifactId>
                <version>${maven-jaxb2-plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <schemaDirectory>
                       ${project.basedir}/src/main/resources/wsdl
                    </schemaDirectory>
                    <schemaIncludes>
                        <include>*.wsdl</include>
                    </schemaIncludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Step 2: To consume a web service, we need a WSDL file. Copy your WSDL file to resources folder.

src/main/resources/wsdl/books.wsdl
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:sch="http://com/kswaughs/services/bookSvc"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:tns="http://com/kswaughs/services/bookSvc"
    targetNamespace="http://com/kswaughs/services/bookSvc">
    <wsdl:types>
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
            elementFormDefault="qualified"
            targetNamespace="http://com/kswaughs/services/bookSvc">
            <xs:element name="getBookRequest">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="name" type="xs:string" />
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:element name="getBookResponse">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="book" type="tns:book" />
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:element name="addBookRequest">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="book" type="tns:book" />
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:element name="addBookResponse">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="status" type="xs:string" />
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:complexType name="book">
                <xs:sequence>
                    <xs:element name="name" type="xs:string" />
                    <xs:element name="author" type="xs:string" />
                    <xs:element name="price" type="xs:string" />
                </xs:sequence>
            </xs:complexType>
        </xs:schema>
    </wsdl:types>
    <wsdl:message name="getBookRequest">
        <wsdl:part element="tns:getBookRequest" name="getBookRequest">
        </wsdl:part>
    </wsdl:message>
    <wsdl:message name="addBookRequest">
        <wsdl:part element="tns:addBookRequest" name="addBookRequest">
        </wsdl:part>
    </wsdl:message>
    <wsdl:message name="addBookResponse">
        <wsdl:part element="tns:addBookResponse" name="addBookResponse">
        </wsdl:part>
    </wsdl:message>
    <wsdl:message name="getBookResponse">
        <wsdl:part element="tns:getBookResponse" name="getBookResponse">
        </wsdl:part>
    </wsdl:message>
    <wsdl:portType name="BooksPort">
        <wsdl:operation name="getBook">
            <wsdl:input message="tns:getBookRequest" name="getBookRequest">
            </wsdl:input>
            <wsdl:output message="tns:getBookResponse" name="getBookResponse">
            </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="addBook">
            <wsdl:input message="tns:addBookRequest" name="addBookRequest">
            </wsdl:input>
            <wsdl:output message="tns:addBookResponse" name="addBookResponse">
            </wsdl:output>
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="BooksPortSoap11" type="tns:BooksPort">
        <soap:binding style="document"
            transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="getBook">
            <soap:operation soapAction="" />
            <wsdl:input name="getBookRequest">
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:output name="getBookResponse">
                <soap:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="addBook">
            <soap:operation soapAction="" />
            <wsdl:input name="addBookRequest">
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:output name="addBookResponse">
                <soap:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="BooksPortService">
        <wsdl:port binding="tns:BooksPortSoap11" name="BooksPortSoap11">
            <soap:address location="http://localhost:8088/MyApp/ws" />
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

Step 3: Generate Domain classes based on Schema defined. When you run Maven build, maven-jaxb2-plugin will generate the java files and stores in target/generated-sources/xjc folder. Configure this folder as source folder in your eclipse IDE.

Step 4: Create Web Service Client Configuration class.

WebSvcClientConfig.java
package com.kswaughs.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.client.core.WebServiceTemplate;

@Configuration
public class WebSvcClientConfig {
    
    @Value("${books.svc.url}")
    private String url;

    @Bean
    Jaxb2Marshaller jaxb2Marshaller() {
    
        Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
        jaxb2Marshaller.setContextPath("com.kswaughs.services.booksvc");

        return jaxb2Marshaller;
    }

    @Bean
    public WebServiceTemplate webServiceTemplate() {
    
        WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
        webServiceTemplate.setMarshaller(jaxb2Marshaller());
        webServiceTemplate.setUnmarshaller(jaxb2Marshaller());
        webServiceTemplate.setDefaultUri(url);

        return webServiceTemplate;
    }

}

Step 5: Create a Service Client class which will consume the web service through above configured WebServiceTemplate class.

BookServiceClient.java
package com.kswaughs.services;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.ws.client.core.WebServiceTemplate;

import com.kswaughs.services.booksvc.AddBookRequest;
import com.kswaughs.services.booksvc.AddBookResponse;
import com.kswaughs.services.booksvc.Book;
import com.kswaughs.services.booksvc.GetBookRequest;
import com.kswaughs.services.booksvc.GetBookResponse;
import com.kswaughs.services.booksvc.ObjectFactory;

@Component
public class BookServiceClient {

    private static final Logger LOGGER = LoggerFactory.getLogger(BookServiceClient.class);

    @Autowired
    private WebServiceTemplate webServiceTemplate;

    public String addBook(String name, String author, String price) {
        
        ObjectFactory factory = new ObjectFactory();
        
        AddBookRequest req = factory.createAddBookRequest();

        Book book = new Book();
        book.setAuthor(author);
        book.setName(name);
        book.setPrice(price);

        req.setBook(book);

        LOGGER.info("Client sending book[Name={},", book.getName());

        AddBookResponse resp = (AddBookResponse) webServiceTemplate.marshalSendAndReceive(req);

        LOGGER.info("Client received status='{}'", resp.getStatus());
        
        return resp.getStatus();
    }

    public Book getBook(String name) {
        
        ObjectFactory factory = new ObjectFactory();
        
        GetBookRequest req = factory.createGetBookRequest();

        req.setName(name);

        LOGGER.info("Client sending book[Name={},", name);

        GetBookResponse resp = (GetBookResponse) webServiceTemplate.marshalSendAndReceive(req);

        LOGGER.info("Client received book='{}'", resp.getBook());
        
        return resp.getBook();
    }

}

Step 6: To test this application, I am creating a sample class which will reads the user entered commands from console and calls the service client methods.

BookCommandLineListener.java
package com.kswaughs.services;

import java.util.Arrays;
import java.util.Scanner;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.kswaughs.services.booksvc.Book;

@Component
public class BookCommandLineListener  {

    private static final Logger LOGGER = LoggerFactory
        .getLogger(BookCommandLineListener.class);

    @Autowired
    private BookServiceClient bookSvcClient;

    @Scheduled(fixedDelay=1000)
    public void run() throws Exception {

        try (Scanner scanner = new Scanner(System.in)) {

            while (true) {

                LOGGER.info("Enter your command");
                String text = scanner.nextLine();

                LOGGER.info("you entered :{}", text);
                String[] args = text.split("\\s+");

                LOGGER.info("args :{}", Arrays.toString(args));

                if ("ADD".equalsIgnoreCase(args[0])) {

                    String name = args[1];
                    String author = args[2];
                    String price = args[3];

                    String status = bookSvcClient.addBook(name, author, price);

                    LOGGER.info("Book Added Status :{}", status);

                } else if ("GET".equalsIgnoreCase(args[0])) {
                    String name = args[1];

                    Book book = bookSvcClient.getBook(name);

                    if(book != null) {
                        LOGGER.info("GET Book Details : Name:{}, Author:{}, Price:{}", 
                                book.getName(), book.getAuthor(), book.getPrice());
                    }

                } else {
                    
                    LOGGER.info("Invalid Command."); 
                }
            }
        }
    }
}

Step 7: Create Spring Boot Main Application Class

SpringBootSoapServiceClientApplication.java
package com.kswaughs;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class SpringBootSoapServiceClientApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(SpringBootSoapServiceClientApplication.class, args);
    }
    
}

Step 8: Configure web service url in application.properties

src/main/resources/application.properties
books.svc.url=http://localhost:8088/MyApp/ws

Testing the Application

Test Add method
2017-12-01 11:19:13.376  INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener     : Enter your command
ADD SpringGuide kswaughs 10
2017-12-01 11:20:27.922  INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener     : you entered :ADD SpringGuide kswaughs 10
2017-12-01 11:20:27.922  INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener     : args :[ADD, SpringGuide, kswaughs, 10]
2017-12-01 11:20:27.922  INFO 10440 --- [pool-1-thread-1] com.kswaughs.services.BookServiceClient  : Client sending book[Name=SpringGuide,
2017-12-01 11:20:28.930  INFO 10440 --- [pool-1-thread-1] com.kswaughs.services.BookServiceClient  : Client received status='SUCCESS'
2017-12-01 11:20:28.930  INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener     : Book Added Status :SUCCESS

Test Get method
2017-12-01 11:20:28.930  INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener     : Enter your command
GET SpringGuide
2017-12-01 11:20:43.711  INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener     : you entered :GET SpringGuide
2017-12-01 11:20:43.711  INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener     : args :[GET, SpringGuide]
2017-12-01 11:20:43.720  INFO 10440 --- [pool-1-thread-1] com.kswaughs.services.BookServiceClient  : Client sending book[Name=SpringGuide,
2017-12-01 11:20:43.740  INFO 10440 --- [pool-1-thread-1] com.kswaughs.services.BookServiceClient  : Client received book='com.kswaughs.services.booksvc.Book@fa6b61e'
2017-12-01 11:20:43.740  INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener     : GET Book Details : Name:SpringGuide, Author:kswaughs, Price:10

Test invalid command
2017-12-01 11:20:43.740  INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener     : Enter your command
ABCD
2017-12-01 11:20:56.944  INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener     : you entered :ABCD
2017-12-01 11:20:56.944  INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener     : args :[ABCD]
2017-12-01 11:20:56.956  INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener     : Invalid Command.

Recommend this on


By kswaughs | Wednesday, March 15, 2017

Spring Boot Soap Web Service Example

This post explains how to develop a soap based web service with Spring Boot. In this Book Store example, We will create a web service that allows to add books information and get the book information.

Step 1: Maven setup

pom.xml
<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>
    <groupId>com.kswaughs.spring</groupId>
    <artifactId>spring_boot_soapwebsvc</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>
    <name>spring_boot_soapwebsvc</name>
    <description>Spring Boot SOAP WEB Service Example</description>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
            <version>1.5.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>wsdl4j</groupId>
            <artifactId>wsdl4j</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>jaxb2-maven-plugin</artifactId>
                <version>1.6</version>
                <executions>
                    <execution>
                        <id>xjc</id>
                        <goals>
                            <goal>xjc</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <schemaDirectory>${project.basedir}/src/main/resources/</schemaDirectory>
                    <outputDirectory>${project.basedir}/src/main/java</outputDirectory>
                    <clearOutputDir>false</clearOutputDir>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Step 2: Create an XML Schema to define the WebService domain. This book store web service provides two operations to add & get Book details.

src/main/resources/books.xsd
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:tns="http://com/kswaughs/services/bookSvc"
    targetNamespace="http://com/kswaughs/services/bookSvc"
    elementFormDefault="qualified">

    <xs:element name="getBookRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="name" type="xs:string" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="getBookResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="book" type="tns:book" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="addBookRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="book" type="tns:book" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    
    <xs:element name="addBookResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="status" type="xs:string" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    
    <xs:complexType name="book">
        <xs:sequence>
            <xs:element name="name" type="xs:string" />
            <xs:element name="author" type="xs:string" />
            <xs:element name="price" type="xs:string" />
        </xs:sequence>
    </xs:complexType>
    
</xs:schema>

Step 3: Generate Domain classes based on Schema defined. When you run Maven build, jaxb2-maven-plugin will generate the java files and stores in src/main/java folder.

Step 4: Create a book repository class to store books details and Initialise the list with few books.

BookRepository.java
package com.kswaughs.repo;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;

import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import com.kswaughs.services.booksvc.Book;

@Component
public class BookRepository {
    
    private static final List<Book> books = new ArrayList<Book>();

    @PostConstruct
    public void initData() {
        Book book1 = new Book();
        book1.setName("The Family Way");
        book1.setAuthor("Tony Parsons");
        book1.setPrice("10 $");
        
        books.add(book1);

        Book book2 = new Book();
        book2.setName("Count To Ten");
        book2.setAuthor("Karen Rose");
        book2.setPrice("12 $");
        
        books.add(book2);
    }

    public Book findBook(String name) {
        Assert.notNull(name);

        Book result = null;

        for (Book book : books) {
            if (name.equals(book.getName())) {
                result = book;
            }
        }

        return result;
    }
    
    public void addBook(Book book) {
        books.add(book);
    }
}

Step 5: Create BookService EndPoint class to handle the incoming SOAP requests.

BookEndPoint.java
package com.kswaughs.services;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;

import com.kswaughs.repo.BookRepository;
import com.kswaughs.services.booksvc.AddBookRequest;
import com.kswaughs.services.booksvc.AddBookResponse;
import com.kswaughs.services.booksvc.GetBookRequest;
import com.kswaughs.services.booksvc.GetBookResponse;

@Endpoint
public class BookEndPoint {
    
    private static final String NAMESPACE_URI = "http://com/kswaughs/services/bookSvc";

    private BookRepository bookRepository;

    @Autowired
    public BookEndPoint(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    // To handle getBookRequest
    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "getBookRequest")
    @ResponsePayload
    public GetBookResponse getBook(@RequestPayload GetBookRequest request) {
        GetBookResponse response = new GetBookResponse();
        response.setBook(bookRepository.findBook(request.getName()));

        return response;
    }
    
    // To handle addBookRequest
    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "addBookRequest")
    @ResponsePayload
    public AddBookResponse addBook(@RequestPayload AddBookRequest request) {
        AddBookResponse response = new AddBookResponse();
        bookRepository.addBook(request.getBook());
        response.setStatus("SUCCESS");
        return response;
    }
}

Step 6: Configure Web Service Spring beans

SpringWebConfig.java
package com.kswaughs.config;

import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;

@EnableWs
@Configuration
public class SpringWebConfig extends WsConfigurerAdapter {
    
    @Bean
    public ServletRegistrationBean messageDispatcherServlet(
            ApplicationContext applicationContext) {
        MessageDispatcherServlet servlet = new MessageDispatcherServlet();
        servlet.setApplicationContext(applicationContext);
        servlet.setTransformWsdlLocations(true);
        return new ServletRegistrationBean(servlet, "/ws/*");
    }

    @Bean(name = "books")
    public DefaultWsdl11Definition defaultBookWsdl11Definition(XsdSchema countriesSchema) {
        DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
        wsdl11Definition.setPortTypeName("BooksPort");
        wsdl11Definition.setLocationUri("/ws");
        wsdl11Definition.setTargetNamespace("http://com/kswaughs/services/bookSvc");
        wsdl11Definition.setSchema(booksSchema());
        return wsdl11Definition;
    }

    @Bean
    public XsdSchema booksSchema() {
        return new SimpleXsdSchema(new ClassPathResource("books.xsd"));
    }
}

Step 7: Create Spring Boot Main Application Class

BootApp.java
package com.kswaughs.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan({ "com.kswaughs.*" })
public class BootApp {
    
    public static void main(String[] args) {
        SpringApplication.run(BootApp.class, args);
    }

}

Step 8: Define application Context path & port in application.properties

src/main/resources/application.properties
server.contextPath=/MyApp
server.port=8088

Testing the Application

WSDL Url : http://localhost:8088/MyApp/ws/books.wsdl

SOAP URL : http://localhost:8088/MyApp/ws

Test 1 : addBookRequest

SOAP Request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:book="http://com/kswaughs/services/bookSvc">
   <soapenv:Header/>
   <soapenv:Body>
      <book:addBookRequest>
         <book:book>
            <book:name>Revolution 2020</book:name>
            <book:author>Chetan Bhagat</book:author>
            <book:price>11 $</book:price>
         </book:book>
      </book:addBookRequest>
   </soapenv:Body>
</soapenv:Envelope>

SOAP Response:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
      <ns2:addBookResponse xmlns:ns2="http://com/kswaughs/services/bookSvc">
         <ns2:status>SUCCESS</ns2:status>
      </ns2:addBookResponse>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Test 2 : getBookRequest

SOAP Request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:book="http://com/kswaughs/services/bookSvc">
   <soapenv:Header/>
   <soapenv:Body>
      <book:getBookRequest>
         <book:name>Revolution 2020</book:name>
      </book:getBookRequest>
   </soapenv:Body>
</soapenv:Envelope>

SOAP Response:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
      <ns2:getBookResponse xmlns:ns2="http://com/kswaughs/services/bookSvc">
         <ns2:book>
            <ns2:name>Revolution 2020</ns2:name>
            <ns2:author>Chetan Bhagat</ns2:author>
            <ns2:price>11 $</ns2:price>
         </ns2:book>
      </ns2:getBookResponse>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Recommend this on


By kswaughs | Tuesday, December 13, 2016

Spring Boot WebSocket Server push + AngularJS client

WebSocket is a very thin, lightweight layer above TCP used to build interactive web applications that send messages back and forth between a browser and the server.

The best examples are live updates websites, where once user access the website neither user nor browser sends request to the server to get the latest updates. Server only keeps sending the messages to the browser.

In this example, I am building a WebSocket application with Spring Boot using STOMP Messaging to provide the cricket live score updates for every 5 secs.

Step 1 : Maven setup

pom.xml
<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.0.RELEASE</version>
    </parent>
    <groupId>com.kswaughs.spring</groupId>
    <artifactId>springboot-websocket</artifactId>
    <version>1.0</version>
    <name>springboot-websocket</name>
    <description>Spring Boot WebSocket Application</description>
    <dependencies>
        <!-- Spring framework related jars -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>1.3.0.RELEASE</version>
        </dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency> 
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Step 2: Model class to hold the batsman & score details

Batsman.java
package com.kswaughs.cricket.beans;

public class Batsman {
    
    private String name;
    
    private int runs;
    
    private int balls;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getRuns() {
        return runs;
    }

    public void setRuns(int runs) {
        this.runs = runs;
    }

    public int getBalls() {
        return balls;
    }

    public void setBalls(int balls) {
        this.balls = balls;
    }
    
}

Step 3: Create a Web Socket & STOMP Messaging Configuration

LiveScoreSocketConfig.java
package com.kswaughs.cricket.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker
public class LiveScoreSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/livescore-websocket").setAllowedOrigins("*"). withSockJS();
    }

}

Step 4: Create a Message handling controller

LiveCricketController.java
package com.kswaughs.cricket.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

import com.kswaughs.cricket.beans.Batsman;
import com.kswaughs.cricket.service.LiveScoreService;

@Controller
public class LiveCricketController {
    
    @Autowired
    private LiveScoreService service;
    
    @MessageMapping("/score")
    @SendTo("/topic/myscores")
    public List<Batsman> getScores() {
        
        List<Batsman> scoresList = service.getScore();
        
        return scoresList;
    }
}

Step 5: Business Logic Implementation

Create a business logic component to get the live updates from back-end service. In this example, this class initializes the list of batsman objects with pre-filled data and later for every request, it will increments the runs & balls by 1 of any one batsman randomly.

LiveScoreService.java
package com.kswaughs.cricket.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.springframework.stereotype.Component;

import com.kswaughs.cricket.beans.Batsman;

@Component
public class LiveScoreService {
    
    private List<Batsman> scoresList = initialScores();
    
    public List<Batsman> getScore() {

        Random rand = new Random();

        int randomNum = rand.nextInt((2 - 1) + 1);
        Batsman batsman = scoresList.get(randomNum);

        batsman.setBalls(batsman.getBalls() + 1);
        batsman.setRuns(batsman.getRuns() + 1);

        return scoresList;
    }

    private List<Batsman> initialScores() {

        Batsman sachin = new Batsman();

        sachin.setName("Sachin Tendulkar");
        sachin.setRuns(24);
        sachin.setBalls(26);

        Batsman ganguly = new Batsman();
        ganguly.setName("Sourav Ganguly");
        ganguly.setRuns(28);
        ganguly.setBalls(30);

        List<Batsman> scoresList = new ArrayList<Batsman>();

        scoresList.add(sachin);
        scoresList.add(ganguly);

        return scoresList;
    }

}

Step 6: Scheduler Configuration

Configure a Scheduler task to send the updated scores to subscribed channel. The task is configured to run every 5 seconds.

ScoreScheduler.java
package com.kswaughs.cricket.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import com.kswaughs.cricket.service.LiveScoreService;

@Configuration
@EnableScheduling
public class ScoreScheduler {
        
    @Autowired
    private SimpMessagingTemplate template;
    
    @Autowired
    LiveScoreService service;

    @Scheduled(fixedRate = 5000)
    public void publishUpdates(){
       
        template.convertAndSend("/topic/myscores", service.getScore());
       
    }
 
}

Step 7 : Create a main class which will initialize and runs the spring boot application.

BootApp.java
package com.kswaughs.cricket.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan({ "com.kswaughs.cricket" })
public class BootApp {
    
    public static void main(String[] args) {
        
        SpringApplication.run(new Object[] { BootApp.class }, args);

    }
}

Testing : AngularJS WebSocket Example

Now we will connect to this application from HTML Page using AngularJS Stomp component and display the live score updates. You can download ng-stomp.standalone.min.js from https://github.com/beevelop/ng-stomp.

scores.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Client</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
<script src="ng-stomp.standalone.min.js"></script>
<script type="text/javascript">
  
  var app = angular.module('kswaughsLiveScore', ['ngStomp']);
 
  app.controller('LiveController', function ($stomp, $scope) {
  
      $scope.myres = [];
  
      $stomp.connect('http://localhost:8080/livescore-websocket', {})
            .then(function (frame) {
                var subscription = $stomp.subscribe('/topic/myscores', 
                    function (payload, headers, res) {
                        $scope.myres = payload;
                        $scope.$apply($scope.myres);
                });
            
                $stomp.send('/app/score', '');
         });
  });
  
 </script>
 <style> 
 .liveScore{
    color : blue;
 }

li{
 list-style: none;
 padding:0px 0px 10px 0px;
}
</style>
</head>
<body >
<div class="liveScore" ng-app="kswaughsLiveScore" ng-controller="LiveController">
   <p>Cricket - Live Score</p>
    <ul>
        <li ng-repeat="x in myres">{{$index+1}} - {{x.name}} - <b>{{x.runs}}</b> runs (<b>{{x.balls}}</b>balls)</li>
    </ul>
</div>    
</body>
</html>

Output

Cricket - Live Score

  • 1 - Sachin Tendulkar - 24 runs ( 26 balls)
  • 2 - Sourav Ganguly - 28 runs ( 30 balls)

The above scores gets updated every 5 secs when the server application runs.

Recommend this on


By kswaughs | Monday, October 10, 2016

Spring Stored Procedure CLOB OUT Example

This example shows how to read CLOB data returned from Stored Procedure using Spring.

Step 1: Oracle Stored Procedure Setup

Stored Procedure
create or replace PROCEDURE GETARTICLE 
(
  IN_ARTICLE_ID IN NUMBER,  
  OUT_ARTICLE_NAME OUT VARCHAR2,  
  OUT_ARTICLE_CONTENT OUT CLOB  
) AS 
BEGIN
  SELECT ARTICLE_NAME , ARTICLE_CONTENT
  INTO OUT_ARTICLE_NAME, OUT_ARTICLE_CONTENT
  from  ARTICLES WHERE ARTICLE_ID = IN_ARTICLE_ID;
END GETARTICLE;

Step 2: Implement SqlReturnType

Create a new class that implements org.springframework.jdbc.core.SqlReturnType to read CLOB data and convert it into String object.

CLOBToStringConverter.java
package com.kswaughs.util;

import java.io.IOException;
import java.io.Reader;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.SQLException;

import org.springframework.jdbc.core.SqlReturnType;

public class CLOBToStringConverter implements SqlReturnType {

    @Override
    public Object getTypeValue(CallableStatement cs,
            int paramIndex, int sqlType, String typeName) throws SQLException {
        
         Clob aClob = cs.getClob(paramIndex);

         final Reader clobReader = aClob.getCharacterStream();
         
         int length = (int) aClob.length();
         char[] inputBuffer = new char[1024];
         final StringBuilder outputBuffer = new StringBuilder();
         try {
            while ((length = clobReader.read(inputBuffer)) != -1)
             {
             outputBuffer.append(inputBuffer, 0, length);
             }
        } catch (IOException e) {
            throw new SQLException(e.getMessage());
        }
         return outputBuffer.toString();
    }
}

Step 3: DAO Implementation

Write a DAO class that extends org.springframework.jdbc.object.StoredProcedure. While declaring the OUT parameters in constructor, pass the above CLOBToStringConverter as parameter to SqlOutParameter.

ArticleDAO.java
package com.kswaughs.dao;

import java.sql.Types;
import java.util.HashMap;
import java.util.Map;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.StoredProcedure;

import com.kswaughs.util.CLOBToStringConverter;

public class ArticleDAO  extends StoredProcedure {
    
    public ArticleDAO(final JdbcTemplate jdbcTemplate) {

        super(jdbcTemplate, "GETARTICLE");
        
        // Input Parameters
        declareParameter(new SqlParameter("IN_ARTICLE_ID", Types.NUMERIC));
        
        // Output Parameters
        declareParameter(new SqlOutParameter("OUT_ARTICLE_NAME", Types.VARCHAR));
        declareParameter(new SqlOutParameter("OUT_ARTICLE_CONTENT", Types.CLOB, 
             null, new CLOBToStringConverter()));
    }
    
    
    public String getArticleContent(final String articleID) throws Exception {
        
        Map returnMap = null;
        
        Map inParams = new HashMap();
        inParams.put("IN_ARTICLE_ID", articleID);
    
        Map returnMap = super.execute(inParams);
        
        if(returnMap== null || returnMap.isEmpty()){
            
            throw new Exception("Article Not found") ;
        }
        
        String articleContent = String.valueOf(returnMap.get("OUT_ARTICLE_CONTENT"));
        return articleContent;
    }    
    
}

Common Mistakes

If we pass typeName as Empty String to SqlOutParameter as below,

 declareParameter(new SqlOutParameter("OUT_ARTICLE_CONTENT", Types.CLOB, 
        "", new CLOBToStringConverter()));

we will get below SQLException

Error occured while executing the Stored procedure. CallableStatementCallback; uncategorized SQLException for SQL [{call GETARTICLE(?, ?, ?)}]; SQL state [99999]; error code [17060]; Fail to construct descriptor: empty Object name; nested exception is java.sql.SQLException: Fail to construct descriptor: empty Object name at com.kswaughs.dao.ArticleDAO.getArticleContent.

Recommend this on


By kswaughs | Tuesday, September 13, 2016

Camel SQL Stored Example

Camel SQL Stored Component is used to call stored procedures.

As a developer, You have to write only a mapper class to transform input/output parameters to your pojo class.

In this example, we will see how to use this component to call stored procedures using an embedded derby database.

Step 1: DataSource Setup

db-schema.sql
CREATE TABLE books (
  BookId VARCHAR(10) NOT NULL,
  BookName VARCHAR(100) NOT NULL,
  author VARCHAR(50) NOT NULL,
  price VARCHAR(20),
  CreateDate VARCHAR(50) NOT NULL
);

// Inserting two books data
INSERT INTO books(BookId, BookName, Author, Price, CreateDate) VALUES 
('FICT3', 'The Ruins', 'Scott Smith', '$9', 'July 18, 2006');
INSERT INTO books(BookId, BookName, Author, Price, CreateDate) VALUES 
('FICT4', 'Velocity', 'Dean Koontz', '$11', 'July 20, 2006');

// Stored procedure to fetch all books
CREATE PROCEDURE GET_ALL_BOOKS() PARAMETER STYLE JAVA LANGUAGE JAVA MODIFIES SQL DATA DYNAMIC RESULT SETS 1 EXTERNAL NAME 'com.kswaughs.db.util.BookStoredProcedure.findAllBooks';

// Stored procedure to fetch requested book
CREATE PROCEDURE GET_BOOK(IN book_name VARCHAR(100)) PARAMETER STYLE JAVA LANGUAGE JAVA MODIFIES SQL DATA DYNAMIC RESULT SETS 1 EXTERNAL NAME 'com.kswaughs.db.util.BookStoredProcedure.findBook';

Stored procedure implementation class is BookStoredProcedure. This is Apache Derby's way of implementation, purely written in Java. This has nothing to do with camel.

BookStoredProcedure.java
package com.kswaughs.db.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.support.JdbcUtils;

public class BookStoredProcedure {

    /**
     * Derby Stored Procedure implementation to read all books 
     * @param bookResults
     * @throws SQLException
     */
    public static void findAllBooks(ResultSet[] bookResults)
            throws SQLException {

        Connection connection = null;
        PreparedStatement statement = null;

        try {
            connection = DriverManager.getConnection("jdbc:default:connection");
            String sql = "select * from books";
            statement = connection.prepareStatement(sql);
            bookResults[0] = statement.executeQuery();
        } finally {
            
            JdbcUtils.closeConnection(connection);
        }

    }
    
    /**
     * Derby Stored Procedure implementation to read requested book
     * @param bookResults
     * @throws SQLException
     */
    public static void findBook(String book_name, ResultSet[] bookResults)
            throws SQLException {

        Connection connection = null;
        PreparedStatement statement = null;

        try {
            connection = DriverManager.getConnection("jdbc:default:connection");
            
            String query = "select * from books where BookName = ?";
                
            statement = connection.prepareStatement(query);
            statement.setString(1, book_name);
            
            bookResults[0] = statement.executeQuery();
        } finally {
            
            JdbcUtils.closeConnection(connection);
        }
    }
}

Step 2: Application Context Configuration

database-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:cxf="http://camel.apache.org/schema/cxf" 
    xmlns:jaxrs="http://cxf.apache.org/jaxrs"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://camel.apache.org/schema/cxf
       http://camel.apache.org/schema/cxf/camel-cxf.xsd 
       http://cxf.apache.org/jaxrs
       http://cxf.apache.org/schemas/jaxrs.xsd
       http://www.springframework.org/schema/jdbc 
       http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
       http://camel.apache.org/schema/spring
       http://camel.apache.org/schema/spring/camel-spring.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context.xsd ">

    <!-- this is the JDBC data source which uses an in-memory only Apache Derby database -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <property name="driverClassName" value="org.apache.derby.jdbc.EmbeddedDriver" />
        <property name="url" value="jdbc:derby:memory:orders;create=true" />
        <property name="username" value="" />
        <property name="password" value="" />
    </bean>

    <jdbc:initialize-database data-source="dataSource" enabled="true">
        <jdbc:script location="classpath:db-schema.sql" />
    </jdbc:initialize-database>

    <!-- configure the Camel SQL Stored component to use the JDBC data source -->
     <bean id="sqlStored" class="org.apache.camel.component.sql.stored.SqlStoredComponent">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="bookMapper" class="com.kswaughs.db.util.BookMapper" />

    <bean id="bookRouter" class="com.kswaughs.router.BookRouter" />
    
    <camelContext id="bookCtx" xmlns="http://camel.apache.org/schema/spring">
    
        <routeBuilder ref="bookRouter" />

    </camelContext>

</beans>

Step 3: Define Routers for calling two stored procedures using Java DSL

BookRouter.java
package com.kswaughs.router;

import org.apache.camel.builder.RouteBuilder;

public class BookRouter extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        
         from("direct:sp")
            .to("sqlStored:GET_ALL_BOOKS()")
            .bean("bookMapper", "readAllBooks")
            .log("${body}");
        
        from("direct:sp-getbook")
            .bean("bookMapper", "buildReqMap")
            .to("sqlStored:GET_BOOK(VARCHAR :#BookName)")
            .bean("bookMapper", "readAllBooks")
            .log("${body}");
    }

}

Step 4: Create POJO class & Row mapper class

Book.java
package com.kswaughs.beans;

public class Book {
    
    private String bookId;
    private String bookName;
    private String author;
    private String price;
    private String createDate;
    
    public String getBookId() {
        return bookId;
    }
    
    public void setBookId(String bookId) {
        this.bookId = bookId;
    }
    
    public String getBookName() {
        return bookName;
    }
    
    public void setBookName(String bookName) {
        this.bookName = bookName;
    }
    
    public String getAuthor() {
        return author;
    }
    
    public void setAuthor(String author) {
        this.author = author;
    }
    
    public String getPrice() {
        return price;
    }
    public void setPrice(String price) {
        this.price = price;
    }
    
    public String getCreateDate() {
        return createDate;
    }
    
    public void setCreateDate(String createDate) {
        this.createDate = createDate;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Book [bookId=");
        builder.append(bookId);
        builder.append(", bookName=");
        builder.append(bookName);
        builder.append(", author=");
        builder.append(author);
        builder.append(", price=");
        builder.append(price);
        builder.append(", createDate=");
        builder.append(createDate);
        builder.append("]");
        return builder.toString();
    }
}

BookMapper.java
package com.kswaughs.db.util;

import com.kswaughs.beans.Book;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BookMapper {
    
    /**
     * Transforms input request to request map
     * @param bookName
     * @return
     */
    public Map<String, Object> buildReqMap(String bookName) {
        Map<String, Object> answer = new HashMap<String, Object>();
        answer.put("BookName", bookName);
        return answer;
    }

        /**
     * Transforms Resultsets into List of Books objects
     * @param resultSets
     * @return
     * @throws Exception
     */
    public List<Book> readAllBooks(Map<String, List<Map<String, String>>> resultSets) 
        throws Exception {
          
        List<Book> books = new ArrayList<Book>();
      
        System.out.println("resultSets:"+resultSets);
        
        Set<String>  keys = resultSets.keySet();
        
        for (String key : keys) {
           
           List<Map<String, String>> rsts = resultSets.get(key);
           
           for (Map<String, String>  rst: rsts) {
            
                Book book = new Book();
                
                book.setBookId(rst.get("BookId"));
                book.setBookName(rst.get("BookName"));
                book.setAuthor(rst.get("Author"));
                book.setPrice(rst.get("Price"));
                book.setCreateDate(rst.get("CreateDate"));
                
                books.add(book);
            }
         }
        
      return books;
    }
}

Step 5: Test the application

CamelBookApp.java
package com.kswaughs.app;

import com.kswaughs.beans.Book;

import java.util.Date;
import java.util.List;

import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.kswaughs.db.util.Book;

public class CamelBookApp {

    public static void main(String[] args) {

        try {
            ApplicationContext springCtx = new ClassPathXmlApplicationContext(
                    "database-context.xml");

            CamelContext context = springCtx.getBean("bookCtx", CamelContext.class);
            
            context.start();
        
            ProducerTemplate producerTemplate = context.createProducerTemplate();
            
             // Fetch all books
            List<Book> resp = producerTemplate.requestBody("direct:sp",  null, List.class);
            System.out.println("SP resp:"+resp);
            
            // Fetch book by name
            List<Book> resp1 = producerTemplate
                 .requestBody("direct:sp-getbook",  "Velocity", List.class);
            System.out.println("SP resp1:"+resp1);
       
        } catch (Exception e) {
          
            e.printStackTrace();
        } 
    }
 
}

Console Logs

 
resultSets:{#result-set-1=[{BOOKID=FICT3, BOOKNAME=The Ruins, AUTHOR=Scott Smith, PRICE=$9, CREATEDATE=July 18, 2006}, {BOOKID=FICT4, BOOKNAME=Velocity, AUTHOR=Dean Koontz, PRICE=$11, CREATEDATE=July 20, 2006}]}
INFO|09/13/2016 11:39:18 980|[Book [bookId=FICT3, bookName=The Ruins, author=Scott Smith, price=$9, createDate=July 18, 2006], Book [bookId=FICT4, bookName=Velocity, author=Dean Koontz, price=$11, createDate=July 20, 2006]]
SP resp:[Book [bookId=FICT3, bookName=The Ruins, author=Scott Smith, price=$9, createDate=July 18, 2006], Book [bookId=FICT4, bookName=Velocity, author=Dean Koontz, price=$11, createDate=July 20, 2006]]
resultSets:{#result-set-1=[{BOOKID=FICT4, BOOKNAME=Velocity, AUTHOR=Dean Koontz, PRICE=$11, CREATEDATE=July 20, 2006}]}
INFO|09/13/2016 11:39:19 000|[Book [bookId=FICT4, bookName=Velocity, author=Dean Koontz, price=$11, createDate=July 20, 2006]]
SP resp1:[Book [bookId=FICT4, bookName=Velocity, author=Dean Koontz, price=$11, createDate=July 20, 2006]]

Maven dependencies

pom.xml
<properties>
    <spring.version>4.1.6.RELEASE</spring.version>
    <camelspring.version>2.16.0</camelspring.version>  
</properties>
    
<dependencies>
    <!-- Camel Dependencies -->
    <dependency>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-core</artifactId>
        <version>${camelspring.version}</version>
    </dependency>
     <dependency>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-cxf</artifactId>
        <version>${camelspring.version}</version>
    </dependency> 
    <dependency>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-spring</artifactId>
        <version>${camelspring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-sql</artifactId>
        <version>2.17.1</version>
    </dependency>
    <!-- End of Camel Dependencies -->

    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.2.2</version>
    </dependency>

    <dependency>
        <groupId>commons-pool</groupId>
        <artifactId>commons-pool</artifactId>
        <version>1.6</version>
    </dependency>
    <dependency>
        <groupId>org.apache.derby</groupId>
        <artifactId>derby</artifactId>
        <version>10.11.1.1</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>    
</dependencies>

Recommend this on