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 | 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 | Friday, April 29, 2016

Spring Pagination Example

Spring MVC Framework provides pagination capabilities for web pages with PagedListHolder class. In this example, we will see how to develop an online phone store application that displays list of the phones available with a specified number of phones and allows user to choose the next or previous list including required page index. Follow the below steps in developing this application.

1. Maven Dependencies

2. Define Service & model classes

3. Define pagination logic in Controller

4. Define Spring MVC Configuration

5. Servlet Configuration

6. JSPs implementation

1. Maven Dependencies

pom.xml
<dependencies>
   <!--Spring framework related jars-->
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>3.2.10.RELEASE</version>
   </dependency>
   <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
   </dependency>
   <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
      <scope>provided</scope>
   </dependency>
</dependencies>

2. Define Service & model classes

Create a Service class that returns the list of phone objects.

PhoneService
package com.kswaughs.web.service;

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

import org.springframework.stereotype.Service;

import com.kswaughs.web.beans.Phone;

@Service
public class PhoneService {
    
    public List<Phone> getPhoneslist() {
        
        List<Phone> phonesList = new ArrayList<Phone>();
        
        phonesList.add(buildPhone("1", "Samsung Galaxy Y"));
        phonesList.add(buildPhone("2", "Nokia Lumia"));
        phonesList.add(buildPhone("3", "Moto G"));
        phonesList.add(buildPhone("4", "Lenovo A 7000 white"));
        phonesList.add(buildPhone("5", "Sony XPeria"));
        
        return phonesList;
    }
    
    private Phone buildPhone(String id, String name) {
        
        Phone phone = new Phone(id, name);
        return phone;
    }
    
}

Below is the Data Model class to hold phone details.

Phone
package com.kswaughs.web.beans;

public class Phone {
    
    private String id;
    
    private String name;
    
    public Phone() {
        
    }
    
    public Phone(String id, String name) {
        this.id = id;
        this.name = name;
    }
    
    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

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

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

}

3. Define pagination logic in Controller

Create a Controller class to handle the requests and define the pagination logic.

PhoneController
package com.kswaughs.web.controller;

import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.support.PagedListHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import com.kswaughs.web.beans.Phone;
import com.kswaughs.web.service.PhoneService;

@Controller
@RequestMapping(value = "/phones")
public class PhoneController {
    
    @Autowired
    PhoneService phoneSvc;
    
    @RequestMapping(value = {"/all/{type}","/all"}, method = RequestMethod.GET)
    public ModelAndView all(
            @PathVariable Map<String, String> pathVariablesMap, 
            HttpServletRequest req) {
        
        PagedListHolder<Phone> productList = null;
        
        String type = pathVariablesMap.get("type");
        
        if(null == type) {
            // First Request, Return first set of list
            List<Phone> phonesList = phoneSvc.getPhoneslist();
            
            productList = new PagedListHolder<Phone>();
            productList.setSource(phonesList);
            productList.setPageSize(2);
            
            req.getSession().setAttribute("phonesList",  productList);
        
            printPageDetails(productList);
            
        } else if("next".equals(type)) {
            // Return next set of list
            productList = (PagedListHolder<Phone>) req.getSession()
                                .getAttribute("phonesList");
            
            productList.nextPage();
            
            printPageDetails(productList);
            
        } else if("prev".equals(type)) {
            // Return previous set of list
            productList = (PagedListHolder<Phone>) req.getSession()
                                .getAttribute("phonesList");
            
            productList.previousPage();
            
            printPageDetails(productList);
            
        } else {
            // Return specific index set of list
            System.out.println("type:" + type);
            
            productList = (PagedListHolder<Phone>) req.getSession()
                                .getAttribute("phonesList");
            
            int pageNum = Integer.parseInt(type);
            
            productList.setPage(pageNum);
            
            printPageDetails(productList);
        }
                    
        ModelAndView mv = new ModelAndView("index");
        
        return  mv;
    }

    private void printPageDetails(PagedListHolder productList) {
        
        System.out.println("curent page - productList.getPage() :"
                + productList.getPage());
        
        System.out.println("Total Num of pages - productList.getPageCount :"
                + productList.getPageCount());
        
        System.out.println("is First page - productList.isFirstPage :"
                + productList.isFirstPage());
        
        System.out.println("is Last page - productList.isLastPage :"
                + productList.isLastPage());
    }
}

4. Define Spring MVC Configuration

Spring Configuration defined with annotations.

SpringWebConfig
package com.kswaughs.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@EnableWebMvc 
@Configuration
@ComponentScan({ "com.kswaughs.web" })
public class SpringWebConfig extends WebMvcConfigurerAdapter {
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }
    
    @Bean
    public ViewResolver jspViewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/jsps/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }        
}

5. Servlet Configuration

Create below servlet initialization class.

SpringWebInitializer
package com.kswaughs.servlet;

import org.springframework.web.servlet.support.
    AbstractAnnotationConfigDispatcherServletInitializer;

import com.kswaughs.config.SpringWebConfig;

public class SpringWebInitializer extends
        AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class[] getServletConfigClasses() {
        return new Class[] { SpringWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected Class[] getRootConfigClasses() {
        return new Class[] {};
    }
    
}

6. JSPs implementation

JSP page that displays the list of phones available.

index.jsp

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
    <head>
    <spring:url value="/resources/css/style.css" var="style" />
    <spring:url value="/phones/all" var="pageurl" />
    <link href="${style}" rel="stylesheet" />
    </head>
    <body>
    <div>
        <p>Welcome to Phones Store</p>
        <p>List of the Phones in our store.</p>
    </div>
    <div>
        <c:set var="pageListHolder" value="${phonesList}" scope="session" />
        <table cellspacing="0">
            <thead><tr><th>ID</th><th>Name</th></tr></thead>
            <tbody>
            <c:forEach var="ph" items="${pageListHolder.pageList}">
                <tr><td>${ph.id}</td><td>${ph.name}</td></tr>
            </c:forEach>
            </tbody>
        </table>
    </div>
    <div>
    <span style="float:left;">
    <c:choose>
        <c:when test="${pageListHolder.firstPage}">Prev</c:when>
        <c:otherwise><a href="${pageurl}/prev">Prev</a></c:otherwise>
    </c:choose>
    </span>
    <span>
    <c:forEach begin="0" end="${pageListHolder.pageCount-1}" varStatus="loop">
    &nbsp;&nbsp;
    <c:choose>
        <c:when test="${loop.index == pageListHolder.page}">${loop.index+1}</c:when>
        <c:otherwise><a href="${pageurl}/${loop.index}">${loop.index+1}</a></c:otherwise>
    </c:choose>
    &nbsp;&nbsp;
    </c:forEach>
    </span>
    <span>
    <c:choose>
        <c:when test="${pageListHolder.lastPage}">Next</c:when>
        <c:otherwise><a href="${pageurl}/next">Next</a></c:otherwise>
    </c:choose>
    </span>
    </div>
    </body>
</html>

css file to display the table with border

styles.css
html, body {
    height: 100%;
}
html {
    display: table;
    margin: auto;
 position: relative;
    min-width: 500px;
    min-height: 768px;
    height: 100%;
}
body {
  display: table-cell;
  font: 13px/20px 'Lucida Grande', Tahoma, Verdana, sans-serif;
  color: #404040;
  background: #ffffff;
  text-align: center;
}
div {
 margin-left: 10 px;
 text-align: left;
}
table {
text-align:center;
font-family:Arial, Helvetica, sans-serif;
color:#000;
font-size:14px;
text-shadow: 1px 1px 0px #fff;
background:#ededed;
margin:20px;
border:#ccc 1px solid;
-moz-border-radius:3px;
-webkit-border-radius:3px;
border-radius:3px;
-moz-box-shadow: 0 1px 2px #d1d1d1;
-webkit-box-shadow: 0 1px 2px #d1d1d1;
box-shadow: 0 1px 2px #d1d1d1;

}
table th {
padding:21px 25px 22px 25px;
border-top:1px solid #fafafa;
border-bottom:1px solid #e0e0e0;
background: #ededed;
background: -webkit-gradient(linear, left top, left bottom, from(#ededed), to(#ebebeb));
background: -moz-linear-gradient(top,  #ededed,  #ebebeb);
}
table a:link {
color: #666;
font-weight: bold;
text-decoration:none;
}
table a:visited {
color: #999999;
font-weight:bold;
text-decoration:none;
}
table a:active,
table a:hover {
color: #bd5a35;
text-decoration:underline;
}
table tr:first-child th:first-child {
-moz-border-radius-topleft:3px;
-webkit-border-top-left-radius:3px;
border-top-left-radius:3px;
}
table tr:first-child th:last-child {
-moz-border-radius-topright:3px;
-webkit-border-top-right-radius:3px;
border-top-right-radius:3px;
}
table tr {
text-align: left;
padding-left:20px;
}
table td:first-child {
text-align: left;
padding-left:20px;
border-left: 0;
}
table td {
padding:18px;
border-top: 1px solid #ffffff;
border-bottom:1px solid #e0e0e0;
border-left: 1px solid #e0e0e0;
background: #ffffff;
background: -webkit-gradient(linear, left top, left bottom, from(#fbfbfb), to(#fafafa));
background: -moz-linear-gradient(top,  #fbfbfb,  #fafafa);
}
table tr.even td {
background: #ffffff;
background: -webkit-gradient(linear, left top, left bottom, from(#f8f8f8), to(#f6f6f6));
background: -moz-linear-gradient(top,  #f8f8f8,  #f6f6f6);
}
table tr:last-child td {
border-bottom:0;
}
table tr:last-child td:first-child {
-moz-border-radius-bottomleft:3px;
-webkit-border-bottom-left-radius:3px;
border-bottom-left-radius:3px;
}
table tr:last-child td:last-child {
-moz-border-radius-bottomright:3px;
-webkit-border-bottom-right-radius:3px;
border-bottom-right-radius:3px;
}
table tr:hover td {
background: #f2f2f2;
background: -webkit-gradient(linear, left top, left bottom, from(#f2f2f2), to(#f0f0f0));
background: -moz-linear-gradient(top,  #f2f2f2,  #f0f0f0);
}

Testing the application

First page

http://localhost:8080/spring_app/phones/all

Recommend this on


By kswaughs | Sunday, April 10, 2016

Spring MVC PDF View Example

Spring MVC Framework has built in methods and components to render the output in many documents like PDF, EXCEL along with JSPs. In this example, we will see how to configure and develop our spring based application to allow users to download the web content in PDF document.

We have an online store application where the home page displays list of the phones available and allows user to choose a phone to get its details. After submission, price details are shown in the next page along with a download link to view the page in PDF format. Now, we will follow the below steps in developing this application.

1. Maven Dependencies

2. Define Controllers, Service & model classes

3. Define Custom PDF View

4. View Configuration

5. Servlet Configuration

6. JSPs implementation

1. Maven Dependencies

pom.xml
<dependencies>
   <!--Spring framework related jars-->
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>3.2.10.RELEASE</version>
   </dependency>
   <!--Itext jar for PDF generation-->
   <dependency>
      <groupId>com.lowagie</groupId>
      <artifactId>itext</artifactId>
      <version>2.1.7</version>
   </dependency>
   <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
   </dependency>
   <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
      <scope>provided</scope>
   </dependency>
</dependencies>

2. Define Controllers, Service & model classes

Create a Controller class to handle the requests based on the URL mappings.

PhoneController
package com.kswaughs.web.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;

import com.kswaughs.web.beans.Phone;
import com.kswaughs.web.service.PhoneService;

@Controller
@RequestMapping(value = "/phones")
@SessionAttributes("phone")
public class PhoneController {
    
    @Autowired
    PhoneService phoneSvc;
    
    @RequestMapping(value = "", method = RequestMethod.GET)
    public ModelAndView home() {
        
        List<Phone> phonesList = phoneSvc.getPhoneslist();
        
        ModelAndView mv = new ModelAndView("home", "phones",  phonesList);
        mv.addObject("phone", new Phone());
            
        return  mv;
    }
    
    @RequestMapping(value = "/details", method = RequestMethod.POST)
    public ModelAndView showPrice(@ModelAttribute("phone") Phone phone ) {
                
        phone = phoneSvc.getPhoneDetails(phone);
                
        return new ModelAndView("details", "phone", phone);
    }
        
    
    @RequestMapping(value = "/details/pdf", method = RequestMethod.GET)
    public ModelAndView showPriceInPDF(@ModelAttribute("phone") Phone phone ) {
                
        List<Phone> phonesList = phoneSvc.getPhoneslist();
        
        return new ModelAndView("pdfView", "phonesList",  phonesList);
    }
}

Create a Service class to process the requests.

PhoneService
package com.kswaughs.web.service;

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

import org.springframework.stereotype.Service;

import com.kswaughs.annotations.TrackResponse;
import com.kswaughs.web.beans.Phone;

@Service
public class PhoneService {
    
    public List<Phone> getPhoneslist() {
        
        List<Phone> phonesList = new ArrayList<Phone>();
        
        phonesList.add(buildPhone("1", "Samsung Galaxy Y"));
        phonesList.add(buildPhone("2", "Nokia Lumia"));
        phonesList.add(buildPhone("3", "Moto G"));
        phonesList.add(buildPhone("4", "Lenovo A 7000 white"));
        phonesList.add(buildPhone("5", "Sony XPeria"));
        
        return phonesList;
    }
    
    public Phone getPhoneDetails(Phone phone) {
                
        final String id = phone.getId();
        String price = null;
        if("1".equals(id)) {
            phone = buildPhone("1", "Samsung Galaxy Y");
            price = "10,000";
            
        } else if("2".equals(id)) {
            phone = buildPhone("2", "Nokia Lumia");
            price = "12,000";
        
        } else if("3".equals(id)) {
            phone = buildPhone("3", "Moto G");
            price = "14,000";
        
        } else if("4".equals(id)) {
            phone = buildPhone("4", "Lenovo A 7000 white");
            price = "9,800";
        
        } else if("5".equals(id)) {
            phone = buildPhone("5", "Sony XPeria");
            price = "23,000";
        }
        
        phone.setPrice(price);
        return phone;
        
    }
    
    private Phone buildPhone(String id, String name) {
        
        Phone phone = new Phone(id, name);
        return phone;
    }
    
}

Below is the Model class to hold phone details.

Phone
package com.kswaughs.web.beans;

public class Phone {
    
    private String id;
    
    private String name;
    
    private String price;
    
    public Phone() {
        
    }
    
    public Phone(String id, String name) {
        this.id = id;
        this.name = name;
    }
    
    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

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

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

}

3. Define Custom PDF View Component

Create a new class that extends AbstractPdfView and override buildPdfDocument() method with data from session object.

PDFBuilder
package com.kswaughs.web.views;

import java.awt.Color;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.document.AbstractPdfView;

import com.lowagie.text.Document;
import com.lowagie.text.Font;
import com.lowagie.text.FontFactory;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import com.kswaughs.web.beans.Phone;

@Component
public class PDFBuilder extends AbstractPdfView {

    @Override
    protected void buildPdfDocument(Map<String, Object> model, Document doc,
            PdfWriter writer, HttpServletRequest req, HttpServletResponse resp)
            throws Exception {

        Phone ph = (Phone) req.getSession().getAttribute("phone");

        doc.add(new Paragraph(
                "Dear User, Following is the list of available phones in our shop."));

        List<Phone> phonesList = (List<Phone>) model.get("phonesList");

        PdfPTable table = new PdfPTable(2);
        table.setSpacingBefore(10);

        // define font for table header row
        Font font = FontFactory.getFont(FontFactory.HELVETICA);
        font.setColor(Color.WHITE);

        // define table header cell
        PdfPCell cell = new PdfPCell();
        cell.setBackgroundColor(Color.BLUE);
        cell.setPadding(5);

        cell.setPhrase(new Phrase("ID", font));
        table.addCell(cell);

        cell.setPhrase(new Phrase("Name", font));
        table.addCell(cell);

        for (Phone phone : phonesList) {
            table.addCell(phone.getId());
            table.addCell(phone.getName());
        }

        doc.add(table);
        
        doc.add(new Paragraph(
                "Please find the price details of phone you have selected."));

        Font priceTxtFont = new Font();
        priceTxtFont.setColor(Color.BLUE);
            
        doc.add(new Paragraph("Phone :" + ph.getName(), priceTxtFont));
        doc.add(new Paragraph("Price :" + ph.getPrice() + "Rs/-", priceTxtFont));

    }

}

4. View Configuration

Add below entry in 'views.properties' file which should be placed in src/main/resources folder. The view name 'pdfView' is mapped to PDFBuilder View component.

views.properties
pdfView.(class)=com.poc.web.views.PDFBuilder

Configure a new ViewResolver with this properties file in Java Configuration

SpringWebConfig
package com.kswaughs.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
import org.springframework.web.servlet.view.ResourceBundleViewResolver;

@EnableWebMvc 
@Configuration
@ComponentScan({ "com.kswaughs.web" })
public class SpringWebConfig extends WebMvcConfigurerAdapter {
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }
    
    @Bean
    public ViewResolver jspViewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/jsps/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
    
    @Bean
    public ViewResolver resourceBundleViewResolver() {
        
        ResourceBundleViewResolver viewResolver = new ResourceBundleViewResolver();
        viewResolver.setBasename("views");
        viewResolver.setOrder(1);
        
        return viewResolver;
    }
        
}

5. Servlet Configuration

Create below servlet initialization class.

SpringWebInitializer
package com.kswaughs.servlet;

import org.springframework.web.servlet.support.
        AbstractAnnotationConfigDispatcherServletInitializer;

import com.kswaughs.config.SpringWebConfig;

public class SpringWebInitializer extends
        AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class[] getServletConfigClasses() {
        return new Class[] { SpringWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected Class[] getRootConfigClasses() {
        return new Class[] {};
    }
    
}

6. JSPs implementation

Home page that displays the list of phones available and allows user to choose one.

home.jsp

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
    <head>
    <spring:url value="/resources/css/style.css" var="style" />
    <spring:url value="/phones/details" var="submiturl" />
    <link href="${style}" rel="stylesheet" />
    </head>
    <body>
    <div>
        <p>Welcome to Phones Store</p>
        <p>Choose your Phone below</p>
        <div>
        <form:form method="post" action="${submiturl}" commandName="phone">
        <c:forEach var="ph" items="${phones}">
            <div><form:radiobutton path="id" value="${ph.id}"/>${ph.name}</div>
        </c:forEach>
            <input type="submit"  value="submit"/>
        </form:form>
        </div>
    </div>
    </body>
</html>

Details page that displays the price details of selected phone.

details.jsp
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
    <head>
        <spring:url value="/resources/css/style.css" var="style" />
        <spring:url value="/phones/details/pdf" var="pdfLink" />
        <link href="${style}" rel="stylesheet" />
    </head>
    <body>
    <div>
        <p>Welcome to Phones Store</p>
        <p>Your phone details</p>
        <div>Phone : ${phone.name}</div>
        <div>Price : ${phone.price} Rs/<sub>-</sub></div>
    </div>
    <div>
        <a href="${pdfLink}">Click here to download in PDF.</a>
    </div>
    </body>
</html>

Testing the application

Home page

http://localhost:8080/spring_app/phones

Welcome to Phones Store
Choose your Phone below
Samsung Galaxy Y
Nokia Lumia
Moto G
Lenovo A 7000 white
Sony XPeria

Details page

http://localhost:8080/spring_app/phones/details

Welcome to Phones Store
Your phone details
Phone : Lenovo A 7000 white
Price : 9,800 Rs/-

On clicking the pdf download link, You will see below content in PDF file.

Recommend this on


By kswaughs | Tuesday, March 22, 2016

Spring Boot Batch Job + Scheduler Example

We have seen how to schedule a task using spring boot and how to run a batch job using spring boot in below posts.

In this example, We will see how to run that Batch job with spring scheduler using spring boot.

Step 1 : By default, the batch job runs immediately when we start the application. So we have to disable the auto run feature in application.properties file.

spring.batch.job.enabled=false

Step 2 : Configure JobLauncher in one more configuration class with defining required dependant components.

BatchScheduler
package com.kswaughs.config;

import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
public class BatchScheduler {

    @Bean
    public ResourcelessTransactionManager transactionManager() {
        return new ResourcelessTransactionManager();
    }

    @Bean
    public MapJobRepositoryFactoryBean mapJobRepositoryFactory(
            ResourcelessTransactionManager txManager) throws Exception {
        
        MapJobRepositoryFactoryBean factory = new 
                MapJobRepositoryFactoryBean(txManager);
        
        factory.afterPropertiesSet();
        
        return factory;
    }

    @Bean
    public JobRepository jobRepository(
            MapJobRepositoryFactoryBean factory) throws Exception {
        return factory.getObject();
    }

    @Bean
    public SimpleJobLauncher jobLauncher(JobRepository jobRepository) {
        SimpleJobLauncher launcher = new SimpleJobLauncher();
        launcher.setJobRepository(jobRepository);
        return launcher;
    }

}

Step 3 : Follow the below methods

Import the above Configuration class to your batch configuration class.

Get the reference of the configured JobLauncher through autowired injection.

Write a new method annotated with @Scheduled and desired cron expression.

Run the JobLauncher with passing job bean and custom job parameter.

BatchConfiguration
package com.kswaughs.config;

import java.util.Date;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.Scheduled;

import com.kswaughs.beans.Order;
import com.kswaughs.beans.SvcReq;
import com.kswaughs.order.OrderSvcInvoker;
import com.kswaughs.processor.JobCompletionNotificationListener;
import com.kswaughs.processor.OrderItemProcessor;

@Configuration
@EnableBatchProcessing
@Import({BatchScheduler.class})
public class BatchConfiguration {

    @Autowired
    private SimpleJobLauncher jobLauncher;

    @Autowired
    public JobBuilderFactory jobBuilderFactory;

    @Autowired
    public StepBuilderFactory stepBuilderFactory;

    @Scheduled(cron = "1 53/3 17 * * ?")
    public void perform() throws Exception {

        System.out.println("Job Started at :" + new Date());

        JobParameters param = new JobParametersBuilder().addString("JobID",
                String.valueOf(System.currentTimeMillis())).toJobParameters();

        JobExecution execution = jobLauncher.run(processOrderJob(), param);

        System.out.println("Job finished with status :" + execution.getStatus());
    }

    @Bean
    public Job processOrderJob() {
        return jobBuilderFactory.get("processOrderJob")
                .incrementer(new RunIdIncrementer())
                .listener(listener())
                .flow(orderStep()).end().build();
    }

    @Bean
    public Step orderStep() {
        return stepBuilderFactory.get("orderStep").<Order, SvcReq> chunk(3)
                .reader(reader()).processor(processor()).writer(writer())
                .build();
    }

    @Bean
    public FlatFileItemReader<Order> reader() {
        FlatFileItemReader<Order> reader = new FlatFileItemReader<Order>();
        reader.setResource(new ClassPathResource("PhoneData.csv"));
        reader.setLineMapper(new DefaultLineMapper<Order>() {
            {
                setLineTokenizer(new DelimitedLineTokenizer() {
                    {
                        setNames(new String[] { "orderID", "orderName" });
                    }
                });
                setFieldSetMapper(new BeanWrapperFieldSetMapper<Order>() {
                    {
                        setTargetType(Order.class);
                    }
                });
            }
        });
        return reader;
    }

    @Bean
    public OrderItemProcessor processor() {
        return new OrderItemProcessor();
    }

    @Bean
    public ItemWriter<SvcReq> writer() {
        return new OrderSvcInvoker();
    }

    @Bean
    public JobExecutionListener listener() {
        return new JobCompletionNotificationListener();
    }

}

In this example, I configured the job to start at '5 PM 53 minutes 1 second' and run for every 3 minutes till 6 PM with cron expression.

Output console logs

Job Started at :Tue Mar 22 17:53:01 IST 2016
2016-03-22 17:53:01.052  INFO 10932 --- [pool-2-thread-1] o.s.b.c.l.support.SimpleJobLauncher: Job: [FlowJob: [name=processOrderJob]] launched with the following parameters: [{JobID=1458649381004}]
2016-03-22 17:53:01.078  INFO 10932 --- [pool-2-thread-1] o.s.batch.core.job.SimpleStepHandler: Executing step: [orderStep]
Converting (Order [orderID=101, orderName=Apple IPhone]) into (SvcReq [id=101, name=APPLE IPHONE])
Converting (Order [orderID=102, orderName=Samsung Galaxy Y]) into (SvcReq [id=102, name=SAMSUNG GALAXY Y])
Converting (Order [orderID=103, orderName=Moto E]) into (SvcReq [id=103, name=MOTO E])
calling web service:SvcResp [id=101, message=APPLE IPHONE Processed successfully]
calling web service:SvcResp [id=102, message=SAMSUNG GALAXY Y Processed successfully]
calling web service:SvcResp [id=103, message=MOTO E Processed successfully]
Processed items:3
Converting (Order [orderID=104, orderName=Moto X]) into (SvcReq [id=104, name=MOTO X])
Converting (Order [orderID=105, orderName=Yuphoria]) into (SvcReq [id=105, name=YUPHORIA])
calling web service:SvcResp [id=104, message=MOTO X Processed successfully]
calling web service:SvcResp [id=105, message=YUPHORIA Processed successfully]
Processed items:2
BATCH JOB FINISHED SUCCESSFULLY
2016-03-22 17:53:01.350  INFO 10932 --- [pool-2-thread-1] o.s.b.c.l.support.SimpleJobLauncher: Job: [FlowJob: [name=processOrderJob]] completed with the following parameters: [{JobID=1458649381004}] and the following status: [COMPLETED]
Job finished with status :COMPLETED

Job Started at :Tue Mar 22 17:56:00 IST 2016
2016-03-22 17:56:01.006  INFO 10932 --- [pool-2-thread-1] o.s.b.c.l.support.SimpleJobLauncher: Job: [FlowJob: [name=processOrderJob]] launched with the following parameters: [{JobID=1458649560996}]
2016-03-22 17:56:01.031  INFO 10932 --- [pool-2-thread-1] o.s.batch.core.job.SimpleStepHandler: Executing step: [orderStep]
Converting (Order [orderID=101, orderName=Apple IPhone]) into (SvcReq [id=101, name=APPLE IPHONE])
Converting (Order [orderID=102, orderName=Samsung Galaxy Y]) into (SvcReq [id=102, name=SAMSUNG GALAXY Y])
Converting (Order [orderID=103, orderName=Moto E]) into (SvcReq [id=103, name=MOTO E])
calling web service:SvcResp [id=101, message=APPLE IPHONE Processed successfully]
calling web service:SvcResp [id=102, message=SAMSUNG GALAXY Y Processed successfully]
calling web service:SvcResp [id=103, message=MOTO E Processed successfully]
Processed items:3
Converting (Order [orderID=104, orderName=Moto X]) into (SvcReq [id=104, name=MOTO X])
Converting (Order [orderID=105, orderName=Yuphoria]) into (SvcReq [id=105, name=YUPHORIA])
calling web service:SvcResp [id=104, message=MOTO X Processed successfully]
calling web service:SvcResp [id=105, message=YUPHORIA Processed successfully]
Processed items:2
BATCH JOB FINISHED SUCCESSFULLY
2016-03-22 17:56:01.154  INFO 10932 --- [pool-2-thread-1] o.s.b.c.l.support.SimpleJobLauncher: Job: [FlowJob: [name=processOrderJob]] completed with the following parameters: [{JobID=1458649560996}] and the following status: [COMPLETED]
Job finished with status :COMPLETED

Recommend this on


By kswaughs | Monday, March 21, 2016

Spring Boot Batch Job Example

Spring Boot provides a lot of utility classes to perform batch jobs and schedule them at regular intervals. This helps developers focus on writing only business logic and hence saves our time and effort.

Spring Batch Framework offers two processing styles.

  • TaskletStep Oriented
  • Chunk Oriented

TaskletStep is used when either only reading or writing the data item is required.

Chunk is used when both reading and writing the data item is required.

In this example, I will explain how to configure batch jobs with Chunk Oriented Processing Model using spring boot.

Chunk Oriented Processing Model involves three components

1.ItemReader - An input operation, Used for providing the data. 
  It reads the data that will be processed.

2.ItemProcessor - A processing operation, Used for item transformation.
  It processes input object and transforms to output object.

3.ItemWriter - An Output operation, Used to write the data transformed by ItemProcessor.
  The data items can be written to database, memory or outputstream.

One item is read from an ItemReader, handed to an ItemProcessor, and transformed into an output object. Once the number of items read equals the commit interval, the entire chunk is written out via the ItemWriter, and then the transaction is committed.

In this sample application, we will read the input data from a csv file and transform into rest service request and call a back-end rest service with 3 data items at a time.

Batch Job involves Job Configuration and Step Configuration.

  • Step Configuration defines the reader, processor, writer and chunk items
  • Job Configuration defines the incrementer, listener and flow Step that we defined above.

Step 1 : Create a batch configuration class as below.

BatchConfiguration
package com.kswaughs.config;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import com.kswaughs.beans.Order;
import com.kswaughs.beans.SvcReq;
import com.kswaughs.order.OrderSvcInvoker;
import com.kswaughs.processor.JobCompletionNotificationListener;
import com.kswaughs.processor.OrderItemProcessor;

@Configuration
@EnableBatchProcessing
public class BatchConfiguration {

    @Autowired
    public JobBuilderFactory jobBuilderFactory;

    @Autowired
    public StepBuilderFactory stepBuilderFactory;
    
    @Bean
    public Job processOrderJob() {
        return jobBuilderFactory.get("processOrderJob")
                .incrementer(new RunIdIncrementer())
                .listener(listener())
                .flow(orderStep())
                .end()
                .build();
    }
    
    @Bean
    public Step orderStep() {
        return stepBuilderFactory.get("orderStep")
                .<Order, SvcReq> chunk(3)
                .reader(reader())
                .processor(processor())
                .writer(writer())
                .build();
    }
    
    @Bean
    public FlatFileItemReader<Order> reader() {
        FlatFileItemReader<Order> reader = new FlatFileItemReader<Order>();
        reader.setResource(new ClassPathResource("PhoneData.csv"));
        reader.setLineMapper(new DefaultLineMapper<Order>() {{
            setLineTokenizer(new DelimitedLineTokenizer() {{
                setNames(new String[] { "orderID", "orderName" });
            }});
            setFieldSetMapper(new BeanWrapperFieldSetMapper<Order>() {{
                setTargetType(Order.class);
            }});
        }});
        return reader;
    }

    @Bean
    public OrderItemProcessor processor() {
        return new OrderItemProcessor();
    }
    
    @Bean
    public ItemWriter<SvcReq> writer() {
        return new OrderSvcInvoker();
    }

   @Bean
    public JobExecutionListener listener() {
        return new JobCompletionNotificationListener();
    }
    
}

Step 2: Below class is the processor that transforms into rest service request.

OrderItemProcessor
package com.kswaughs.processor;

import org.springframework.batch.item.ItemProcessor;

import com.kswaughs.beans.Order;
import com.kswaughs.beans.SvcReq;

public class OrderItemProcessor implements ItemProcessor<Order, SvcReq> {
    
    @Override
    public SvcReq process(final Order order) throws Exception {
      
        final SvcReq svcReq = new SvcReq();
        
        svcReq.setId(order.getOrderID());
        svcReq.setName(order.getOrderName().toUpperCase());

        System.out.println("Converting (" + order + ") into (" + svcReq + ")");

        return svcReq;
    }

}

Step 3: Below class is the Item Writer that calls the rest service.

OrderSvcInvoker
package com.kswaughs.order;

import java.util.List;

import org.springframework.batch.item.ItemWriter;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import com.kswaughs.beans.SvcReq;
import com.kswaughs.beans.SvcResp;

public class OrderSvcInvoker implements ItemWriter<SvcReq> {

    @Override
    public void write(List<? extends SvcReq> svcReqs) throws Exception {
        
        for (SvcReq svcReq : svcReqs) {
            
            RestTemplate restTemplate = new RestTemplate();
            
            ResponseEntity<SvcResp> respEntity = restTemplate
                .postForEntity("http://localhost:8080/phone/order", svcReq, SvcResp.class);
            
            SvcResp resp = respEntity.getBody();
            
            System.out.println("calling web service:" + resp);
        }
        
        System.out.println("Processed items:" + svcReqs.size());
    }

}

Step 4: Below class is the listener class that is executed when the job is finished.

JobCompletionNotificationListener
package com.kswaughs.processor;

import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.listener.JobExecutionListenerSupport;

public class JobCompletionNotificationListener extends JobExecutionListenerSupport {

    @Override
    public void afterJob(JobExecution jobExecution) {
        if(jobExecution.getStatus() == BatchStatus.COMPLETED) {
            System.out.println("BATCH JOB FINISHED SUCCESSFULLY");
        }
    }
}

Step 5: Below are the value bean classes for Item, rest request and rest response.

Order
package com.kswaughs.beans;

public class Order {
    
    private String orderID;
    private String orderName;

    public String getOrderID() {
        return orderID;
    }

    public void setOrderID(String orderID) {
        this.orderID = orderID;
    }

    public String getOrderName() {
        return orderName;
    }

    public void setOrderName(String orderName) {
        this.orderName = orderName;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Order [orderID=");
        builder.append(orderID);
        builder.append(", orderName=");
        builder.append(orderName);
        builder.append("]");
        return builder.toString();
    }

}

SvcReq
package com.kswaughs.beans;

public class SvcReq {
    
    private String id;
    
    private String name;

    public String getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("SvcReq [id=");
        builder.append(id);
        builder.append(", name=");
        builder.append(name);
        builder.append("]");
        return builder.toString();
    }
}

SvcResp
package com.kswaughs.beans;

public class SvcResp {
    
    private String id;
    
    private String message;

    public String getId() {
        return id;
    }

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

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("SvcResp [id=");
        builder.append(id);
        builder.append(", message=");
        builder.append(message);
        builder.append("]");
        return builder.toString();
    }
}

Main class

BootApp
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(new Object[] { BootApp.class }, args);

    }

}

Input csv file : PhoneData.csv

101,Apple IPhone
102,Samsung Galaxy Y
103,Moto E
104,Moto X
105,Yuphoria

Output:

2016-03-16 13:58:53.324  INFO 12876 --- [main] o.s.b.c.l.support.SimpleJobLauncher: Job: 
[FlowJob: [name=processOrderJob]] launched with the following parameters: [{run.id=1}]
2016-03-16 13:58:53.343  INFO 12876 --- [main] o.s.batch.core.job.SimpleStepHandler: Executing step: [orderStep]
Converting (Order [orderID=101, orderName=Apple IPhone]) into (SvcReq [id=101, name=APPLE IPHONE])
Converting (Order [orderID=102, orderName=Samsung Galaxy Y]) into (SvcReq [id=102, name=SAMSUNG GALAXY Y])
Converting (Order [orderID=103, orderName=Moto E]) into (SvcReq [id=103, name=MOTO E])
calling web service:SvcResp [id=101, message=APPLE IPHONE Processed successfully]
calling web service:SvcResp [id=102, message=SAMSUNG GALAXY Y Processed successfully]
calling web service:SvcResp [id=103, message=MOTO E Processed successfully]
Processed items:3
Converting (Order [orderID=104, orderName=Moto X]) into (SvcReq [id=104, name=MOTO X])
Converting (Order [orderID=105, orderName=Yuphoria]) into (SvcReq [id=105, name=YUPHORIA])
calling web service:SvcResp [id=104, message=MOTO X Processed successfully]
calling web service:SvcResp [id=105, message=YUPHORIA Processed successfully]
Processed items:2
BATCH JOB FINISHED SUCCESSFULLY
2016-03-16 13:58:53.786  INFO 12876 --- [main] o.s.b.c.l.support.SimpleJobLauncher: Job: 
[FlowJob: [name=processOrderJob]] completed with the following parameters: [{run.id=1}] and the following status: [COMPLETED]

Maven POM

<dependencies>
    <!-- Spring framework related jars -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>1.3.0.RELEASE</version>
    </dependency>
    <!-- Batch related jars -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-batch</artifactId>
        <version>1.2.7.RELEASE</version>
    </dependency>
    <!-- Rest service call related jars -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-rest</artifactId>
        <version>1.3.0.RELEASE</version>
        <exclusions>
              <exclusion>
                   <groupId>org.springframework.boot</groupId>
                   <artifactId>spring-boot-starter-web</artifactId>
               </exclusion>
           </exclusions> 
    </dependency>
</dependencies>

Recommend this on