Showing posts with label maven. Show all posts
Showing posts with label maven. Show all posts

Wednesday, December 28, 2016

Docker for Java development - simple example

1.Intro

Docker site: https://www.docker.com/

From wikipedia:
Docker is an open-source project that automates the deployment of Linux applications inside software containers. Quote of features from Docker web pages:
Docker containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries – anything you can install on a server. This guarantees that it will always run the same, regardless of the environment it is running in.[5]
Docker provides an additional layer of abstraction and automation of operating-system-level virtualization on Linux.[6] Docker uses the resource isolation features of the Linux kernel such as cgroups and kernel namespaces, and a union-capable file system such as OverlayFS and others[7] to allow independent "containers" to run within a single Linux instance, avoiding the overhead of starting and maintaining virtual machines.[8]

In shorts, docker is a way of creating containers, deploying applications into containers and  running application inside container. What is container in a docker world? It's a some kind of virtual PC but very lightweight and easy to use.

2. Docker installation

Of course, first of all, docker has to be installed. On official site there are instructions for different operational systems: https://docs.docker.com/engine/installation/

After installation you can run

# docker run hello-world

And result should be something like:

Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

As you can see, a lot of magic happen by executing just one command.

2. Main concepts/components.

In previous step, Docker's output gave a lot of information how it works:
 - where are docker images which are located in Docker Hub.
 - if we don't have needed image locally - it will be pulled down from Docker Hub
 - based on this image Docker is creating container which it may run

To check list of downloade images we can execute:
# docker images

In result will be present just downloaded hello-world image:

REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
hello-world               latest              c54a2cc56cbb        6 months ago        1.848 kB


3. Test application for running inside docker container

Now let's create a simple application(in JAR file) which we want to put inside a container. 
I generated simple maven SpringBoot application on http://start.spring.io/ : I selected "Web" in dependencies and pressed button "generate project".

In downloaded project we need just 2 files: 

pom.xml
- I removed test dependency and specified jar file name(MyTestApp):


<?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.demien</groupId>
   <artifactId>demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>demo</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.4.2.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>
   </properties>

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

   </dependencies>

   <build>
                <finalName>MyTestApp</finalName>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>


</project>

 
And Main application runner DemoApplication.java

package com.demien;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController@SpringBootApplicationpublic class DemoApplication {

   @RequestMapping("info")
   public String info() {
      return "Hello world!";
   }

   public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
   }
}

- I just added one endpoint:   /info which is returning "Hello world!" message.
We can now run our application by running from command line: mvn spring-boot:run
and open URL: http://localhost:8080/info
 - result should be "Hello world!"

4. Putting test application into container

4.1 Dockerfile

Now we have to create a container. First of all we have to create a file with container description. File name should be "Dockerfile" and it content in this example is:

FROM maven
ADD target/MyTestApp.jar /usr/local/app/MyTestApp.jar
EXPOSE 8080
CMD ["java", "-jar", "/usr/local/app/MyTestApp.jar"]  

Let's explain it step by step:

FROM maven 
- base image for container is MAVEN. List of all imaged can be queried by command "docker search XXX". For example:
# docker search java

NAME                   DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
java                   Java is a concurrent, class-based, and obj...   1246      [OK]      
anapsix/alpine-java    Oracle Java 8 (and 7) with GLIBC 2.23 over...   168                  [OK]
develar/java                                                           53                   [OK]
isuper/java-oracle     This repository contains all java releases...   47                   [OK]
lwieske/java-8         Oracle Java 8 Container - Full + Slim - Ba...   30                   [OK]
nimmis/java-centos     This is docker images of CentOS 7 with dif...   20                   [OK]

Next line  from Dockerfile:
ADD target/MyTestApp.jar /usr/local/app/MyTestApp.jar
- to image from previous step we have to add our application JAR file: file has to be taken from ./target directory and put into /usr/local/app directory in image filesystem

EXPOSE 8080
- our application is using port 8080

CMD ["java", "-jar", "/usr/local/app/MyTestApp.jar"] 
- command which will be executed when we will ask docker to run our container.

4.2 Building new container image

After Dockerfile creation, we can create a new image based on it content:
#docker build -t demien/docker-test1 .

Docker will read Dockerfile in a current directory and create a new image: demien/docker-test1
During build phase, docker will download MAVEN image and all images on which it depends. Now we can execute again:
# docker images
 - to check. New image should be present in result list:

REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
demien/docker-test1       latest              108d927b5ebb        5 days ago          667.4 MB
<none>                    <none>              ffd0c9386045        5 days ago          667.4 MB
<none>                    <none>              b41d536b6005        5 days ago          667.4 MB
<none>                    <none>              9ce7e4000959        5 days ago          667.4 MB
<none>                    <none>              3a3615d1befb        5 days ago          667.4 MB
<none>                    <none>              f5d4c54045dc        5 days ago          667.4 MB
<none>                    <none>              04dde1d25ea3        6 days ago          707.5 MB
maven                     latest              a90451161cc5        9 days ago          653.2 MB
hello-world               latest              c54a2cc56cbb        6 months ago        1.848 kB


5. The end

We're almost done! Just one thing: we have to run our created image by executing from command line:
#docker run -p 8080:8080 demien/docker-test1

Parameter  "-p 8080:8080" means that we are redirecting our local port 8080 to port 8080 of container.

After start of our application we can open http://localhost:8080/info - result should be again "Hello world!". But this result we got from container: our request to endpoint "/info" on port 8080 was forwarded to port 8080 of our container which is running MyTestApp.jar located at it local directory "/usr/local/app" (we defined it by CMD ["java", "-jar", "/usr/local/app/MyTestApp.jar"]). And all this magic happen by Docker, invisible for us!

All source files can be downloaded from here

Monday, July 7, 2014

Rest(SpringMVC) application with XML-less Spring, Hibernate and embedded jetty, Unit and Integration tests

Not so long ago I was asked to write test application for working with Loans. Conditions :
1. XML-less spring
2. SpringMVC for rests
3. Two entity in application:Loan and User. Loan Can be extended with rate 1.5*previous rate. In case of more than 3 operation from one IP - reject operation.
4. Code have to be covered by unit and integration tests with ability for being executed from command line.

May be it will be useful for somebody, so I decided to publish it. Created application is very big so in post I will show only main classes, but entire application can be downloaded by link provided at the end of the post.

1. 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>

    <groupId>com.demien</groupId>
    <artifactId>springmvcrest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Spring MVC Rest example</name>

    <properties>
        <spring.version>3.2.0.RELEASE</spring.version>
        <jetty.version>8.1.15.v20140411</jetty.version>
        <slf4j.version>1.7.5</slf4j.version>
        <hibernate.version>4.2.5.Final</hibernate.version>
        <h2.version>1.3.173</h2.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <integration.test.package>**/com/demien/springmvcrest/integration/test/**</integration.test.package>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
            <version>1.0</version>
        </dependency>
        <!-- HIBERNATE -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <!-- H2 DB -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>${h2.version}</version>
        </dependency>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>commons-logging</artifactId>
                    <groupId>commons-logging</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>       

        <!-- NOXML Sping library -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>2.2</version>
        </dependency>

        <!-- Jetty embedded -->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-servlet</artifactId>
            <version>${jetty.version}</version>
        </dependency>

        <!-- Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${slf4j.version}</version>
        </dependency>

        <!-- JSON marshaller -->
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-jaxrs</artifactId>
            <version>1.9.11</version>
        </dependency>

        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>1.9.11</version>
        </dependency>

        <!-- Testing -->

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.3.3</version>
        </dependency>

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.9.5</version>
        </dependency>

    </dependencies>


    <build>
        <finalName>springmvcrest</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.5</version>
 
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.13</version>
                <configuration>
                    <includes>
                        <include>**/*IT.java</include>
                    </includes>
                </configuration>
                <executions>
                    <execution>
                        <id>failsafe-integration-tests</id>
                        <phase>integration-test</phase>
                        <goals>
                            <goal>integration-test</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

    <profiles>
        <profile>
            <id>integration</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <version>2.5</version>
                        <configuration>
                            <excludes>
                                <exclude>**/*$*</exclude>
                            </excludes>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

</project>


2. domains

Domains objects are very simple, so only one(of two) will be shown.

First, simple interface for entities which have to be stored in DB:
public interface IPersistable {
    Integer getId();
    void setId(Integer id);
}

Loan entity:
package com.demien.springmvcrest.domain;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Entity;

@Entity(name = "LOAN")
public class Loan implements Serializable, IPersistable {

    public static enum STATE {
        OPEN, CLOSED, EXTENDED
    }

    private static final long serialVersionUID = 7566184847780781908L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID")
    private Integer id;

    @Column(name = "IP_ADDR")
    private String ipAddr;

    @Column(name = "AMOUNT")
    private Float amount;

    @Column(name = "RATE")
    private Float rate;

    @Column(name = "STATE")
    private String state;

    @Column(name = "DEADLINE")
    private Date deadLine;

    @Column(name = "PARENT_ID")
    private Integer parentId;

    @Column(name = "CREATE_DATE")
    private Date createDate;

    @Column(name = "CHANGE_DATE")
    private Date changeDate;
    
    @ManyToOne(cascade=CascadeType.ALL)
    @JoinColumn(name= "USER_ID")
    private User user;

    public Loan() {
    }

    public Integer getId() {
        return id;
    }

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

    public String getIpAddr() {
        return ipAddr;
    }

    public void setIpAddr(String ipAddr) {
        this.ipAddr = ipAddr;
    }

    public Float getAmount() {
        return amount;
    }

    public void setAmount(Float amount) {
        this.amount = amount;
    }

    public Float getRate() {
        return rate;
    }

    public void setRate(Float rate) {
        this.rate = rate;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public Date getDeadLine() {
        return deadLine;
    }

    public void setDeadLine(Date deadLine) {
        this.deadLine = deadLine;
    }

    public Integer getParentId() {
        return parentId;
    }

    public void setParentId(Integer parentId) {
        this.parentId = parentId;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Date getChangeDate() {
        return changeDate;
    }

    public void setChangeDate(Date changeDate) {
        this.changeDate = changeDate;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Loan clone() {
        Loan loan = new Loan();
        loan.setAmount(new Float(this.getAmount()));
        loan.setDeadLine(new Date(this.getDeadLine().getTime()));
        loan.setIpAddr(new String(this.getIpAddr()));
        if (this.getParentId() != null) {
            loan.setParentId(new Integer(this.getParentId()));
        }
        loan.setRate(new Float(this.getRate()));
        loan.setState(new String(this.getState()));
        loan.setCreateDate(new Date());
        return loan;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Loan)) {
            return false;
        } else {
            Loan loan = (Loan) object;
            if (loan.getId().equals(this.getId())
                    && loan.getIpAddr().equals(this.getIpAddr())
                    && loan.getCreateDate().equals(this.getCreateDate())) {
                return true;
            }
            return false;
        }
    }

    @Override
    public String toString() {
        return "Loan [id=" + id + ", ipAddr=" + ipAddr + ", amount=" + amount
                + ", rate=" + rate + ", state=" + state + ", deadLine="
                + deadLine + ", parentId=" + parentId + ", createDate="
                + createDate + ", changeDate=" + changeDate + "]";
    }
    
    @Override
    public int hashCode() {
        return getId();
    }
    

}

3. DAO

- Used generic dao, like in my previous posts.

3.1 Interface

package com.demien.springmvcrest.dao;

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

public interface IBaseDAO<T> {
    public T get(Integer id);
    public T save(T object);
    public void update(T object);
    public void delete(T object);
    public List<T> query(String hsql, Map<String, Object> params);

}

3.2 Implementation

package com.demien.springmvcrest.dao;

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

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

public class BaseDAOImpl<T> implements IBaseDAO<T> {
    private SessionFactory sessionFactory;
    private Class<T> cl;

    public BaseDAOImpl(Class<T> cl, SessionFactory sessionFactory) {
        this.cl=cl;
        this.sessionFactory = sessionFactory;
    }

    @Override
    public T get(Integer id) {
        Session session = sessionFactory.getCurrentSession();
        @SuppressWarnings("unchecked")
        T element = (T) session.get(cl, id);
        return element;
    }

    @SuppressWarnings("unchecked")
    @Override
    public T save(T object) {
        Session session = sessionFactory.getCurrentSession();
        T result=(T)session.save(object);
        return result;
    }

    @Override
    public void update(T object) {
        Session session = sessionFactory.getCurrentSession();
        session.update(object);
    }

    @Override
    public void delete(T object) {
        Session session = sessionFactory.getCurrentSession();
        session.delete(object);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> query(String hsql, Map<String, Object> params) {
        Session session = sessionFactory.getCurrentSession();
        Query query = session.createQuery(hsql);
        if (params != null) {
            for (String i : params.keySet()) {
                query.setParameter(i, params.get(i));
            }
        }

        List<T> result = null;
        if ((hsql.toUpperCase().indexOf("DELETE") == -1)
                && (hsql.toUpperCase().indexOf("UPDATE") == -1)
                && (hsql.toUpperCase().indexOf("INSERT") == -1)) {
            result = query.list();
        } else {
            query.executeUpdate();
        }
        return result;
    }

}


4. Service level

The same as in y previous posts: BaseService(interface and implementation) for common actions and "specific" services for  "specific" actions related with entities - only one of two will be shown.

4.1 BaseService interface

package com.demien.springmvcrest.service;

import java.util.List;

import com.demien.springmvcrest.dao.IBaseDAO;

public interface IBaseService<T> extends IBaseDAO<T> {
  List<T> getAll();
  void deleteAll();
}

4.2 BaseService implementation

package com.demien.springmvcrest.service;

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

import org.springframework.transaction.annotation.Transactional;

import com.demien.springmvcrest.dao.IBaseDAO;

public abstract class BaseServiceImpl<T> implements IBaseService<T> {
    
    private IBaseDAO<T> dao;
    private Class<T> cl;
    
    
    public BaseServiceImpl(Class<T> cl) {
        this.cl=cl;
    }
    
    public void setDao(IBaseDAO<T> dao) {
        this.dao=dao;
    }
    
    
    @Transactional
    @Override
    public T get(Integer id) {
        return (T) dao.get(id);
    }

    @Transactional
    @Override
    public T save(T object) {
        T result=(T)dao.save(object);
        return result;
    }

    @Transactional
    @Override
    public void update(T object) {
        dao.update(object);     
    }

    @Transactional
    @Override
    public void delete(T object) {
        dao.delete(object);     
    }

    @Transactional
    @Override
    public List<T> query(String hsql, Map<String, Object> params) {
        return (List<T>)dao.query(hsql, params);
    }

    @Transactional
    @Override
    public List<T> getAll() {
        return query("from "+cl.getName(), null);
    }

    @Transactional
    @Override
    public void deleteAll() {
        query("delete from "+cl.getName(),null);
        
    }

}


4.2 Loan service interface - added specific to Loan entity methods:


package com.demien.springmvcrest.service;

import com.demien.springmvcrest.domain.Loan;

public interface ILoanService extends IBaseService<Loan> {
    
    void extendLoan(Integer id);

    String checkLoan(final String ipAddr, final Float amount);

    void setCheckHourFrom(final int checkHourFrom);

    void setCheckHourTo(final int checkHourTo);
}

4.3 Loan Service implementation

package com.demien.springmvcrest.service;

import static com.demien.springmvcrest.AppConst.CHECK_MAX_AMOUNT;
import static com.demien.springmvcrest.AppConst.CHECK_MAX_IP_DAY_REQUESTS;
import static com.demien.springmvcrest.AppConst.REJECT_REASON_BAD_TIME;
import static com.demien.springmvcrest.AppConst.REJECT_REASON_IP_DAY_ATTEMPTS;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.demien.springmvcrest.AppConst;
import com.demien.springmvcrest.dao.IBaseDAO;
import com.demien.springmvcrest.domain.Loan;

@Service
public class LoanServiceImpl extends BaseServiceImpl<Loan> implements ILoanService {
    
    public final static String QUERY_IP_COUNT="from "+Loan.class.getName()+" where ipAddr=:ipAddr and createDate>=:dateFrom and createDate<=:dateTo";
    private static int checkHourFrom;
    private static int checkHourTo;

    public LoanServiceImpl() {
        super(Loan.class);
    }

    @Autowired
    @Qualifier("loanDAOImpl")
    public void setDao(final IBaseDAO<Loan> dao) {
        super.setDao(dao);    
    }
    

    public void setCheckHourFrom(final int checkHourFrom) {
        LoanServiceImpl.checkHourFrom = checkHourFrom;
    }


    public void setCheckHourTo(final int checkHourTo) {
        LoanServiceImpl.checkHourTo = checkHourTo;
    }


    @SuppressWarnings("deprecation")
    public boolean isTimeInHourInterval(final int from, final int to) {
        Date d = new Date();
        int hour = d.getHours();

        if (hour >= from && hour <= to) {
            return true;
        } else {
            return false;
        }

    }

    @SuppressWarnings("deprecation")
    public boolean isDateToday(final Date loanDate) {
        Date d = new Date();
        if (loanDate.getYear() == d.getYear()
                && loanDate.getMonth() == d.getMonth()
                && loanDate.getDay() == d.getDay()) {
            return true;
        } else {
            return false;
        }

    }

    public int getIpRequestTodayCount(final String ipAddr) {
        
        Map<String,Object> params=new HashMap<String,Object>(); 
        params.put("ipAddr", ipAddr);
        Date dateTo=new Date();
        Date dateFrom=new Date(dateTo.getTime()-AppConst.DAY_SECONDS);
        params.put("dateFrom", dateFrom);
        params.put("dateTo", dateTo);
        List<Loan> loans=query(QUERY_IP_COUNT, params);
        
        return loans.size();
    }

    @Transactional
    @Override
    public String checkLoan(final String ipAddr, final Float amount) {
        StringBuilder result = new StringBuilder();
        // 1 check by max amount and hours
        if (amount >= CHECK_MAX_AMOUNT && isTimeInHourInterval(checkHourFrom, checkHourTo)) {
            result.append(REJECT_REASON_BAD_TIME);
        }
        // 2 check by ip addr
        if (getIpRequestTodayCount(ipAddr) >= CHECK_MAX_IP_DAY_REQUESTS) {
            result.append(REJECT_REASON_IP_DAY_ATTEMPTS);
        }

        return result.toString();
    }    

    @Transactional
    @Override
    public void extendLoan(final Integer id) {
        Loan oldLoan=get(id);
        Loan newLoan=oldLoan.clone();
        oldLoan.setState(Loan.STATE.EXTENDED.toString());
        oldLoan.setChangeDate(new Date());

        newLoan.setRate(newLoan.getRate()*AppConst.EXTEND_RATE_MUL);
        Date d=new Date();
        d.setTime(d.getTime()+AppConst.EXTEND_INTERVAL);
        newLoan.setDeadLine(d);
        newLoan.setParentId(id);
        update(oldLoan);
        save(newLoan);
    }

}

5. Controllers

5.1 BaseController - for common CRUD operations on entities.

package com.demien.springmvcrest.controller;


import java.util.List;

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.demien.springmvcrest.domain.IPersistable;
import com.demien.springmvcrest.service.IBaseService;
import com.demien.springmvcrest.utils.JsonHelper;
import com.demien.springmvcrest.utils.JsonHelper.JsonHelperException;

import static com.demien.springmvcrest.AppConst.*;

public class BaseController<T extends IPersistable> {

    private IBaseService<T> service;
    
    public static final String SERVICE_HELLO="sayhello";
    public static final String SERVICE_GET_ALL="getall";
    public static final String SERVICE_GETBYID="getbyid";
    public static final String SERVICE_ADD="add";
    public static final String SERVICE_UPDATE="update";
    public static final String SERVICE_DELETE="delete";
    
    
    private Class<T> cl;
    
    public BaseController(Class<T> cl) {
        this.cl=cl;
    }
    
    public void setService(IBaseService<T> service) {
        this.service = service;
    }


    @RequestMapping(method=RequestMethod.GET, value="/sayhello")
    @ResponseBody
    public String sayHello() {
        System.out.println("Executing sayHello");
        return HELLO;
    }
    
    
    @RequestMapping(method=RequestMethod.GET, value="/getall")
    public @ResponseBody String getAll() throws JsonHelperException  {
        List<T> list = service.getAll();
        String json=JsonHelper.object2json(list);
        return json;
    }

    private T getEntityById(Integer id) {
        
        return service.get(id);
        
    }

    @RequestMapping(method=RequestMethod.GET, value="/getbyid")
    public @ResponseBody String getEntity(@RequestParam("id") Integer id) throws JsonHelperException {
        T result =service.get(id);
        String resultJson=JsonHelper.object2json(result);
        return resultJson;
    }
    
        protected T addEntity(T entity) {
        Object o=service.save(entity);
        if (o instanceof Integer) {
            Integer id=(Integer)o;
            entity.setId(id);
        }
        return entity;
    }

    @SuppressWarnings("unchecked")  
    @RequestMapping(method=RequestMethod.POST, value="/add")
    public @ResponseBody String addEntity(@RequestBody String json) throws JsonHelperException  {
        T entity=(T)JsonHelper.Json2Object(json, cl);
        
        if (entity==null) {
            throw new RuntimeException("addEnity Error: entity is null");
        }
        
        T searchResult = getEntityById(entity.getId());
        if (searchResult == null) {
            entity=addEntity(entity);
        }
        String resultJson=JsonHelper.object2json(entity);
        return resultJson;
    }
    
    protected void updateEntity(T entity) {
        service.update(entity); 
    }

    @SuppressWarnings("unchecked")
    @RequestMapping(method=RequestMethod.POST, value="/update")
    public @ResponseBody String updateEntity(@RequestBody String json) throws JsonHelperException {     
        T entity=(T)JsonHelper.Json2Object(json, cl);
        updateEntity(entity);
        String resultJson=JsonHelper.object2json(entity);
        return resultJson;
    }
    
    protected void deleteEntity(T entity) {
        service.delete(entity);
    }

    @SuppressWarnings("unchecked")
    @RequestMapping(method=RequestMethod.POST, value="/delete")
    public @ResponseBody void deleteEntity(@RequestBody String json) throws JsonHelperException {
        T entity=(T)JsonHelper.Json2Object(json, cl);
        deleteEntity(entity);
    }

}

5.2. "specific" controller example  - Loan Controller.

package com.demien.springmvcrest.controller;

import static com.demien.springmvcrest.AppConst.*;

import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.demien.springmvcrest.domain.Loan;
import com.demien.springmvcrest.domain.User;
import com.demien.springmvcrest.service.ILoanService;
import com.demien.springmvcrest.service.IUserService;
import com.demien.springmvcrest.utils.JsonHelper;
import com.demien.springmvcrest.utils.JsonHelper.JsonHelperException;

@Controller
@RequestMapping(value = "/loan")
public class LoanController extends BaseController<Loan> {
    
    private ILoanService loanService;
    private IUserService userService;


    // services
    public static final String SERVICE_MAIN = "loan";
    public static final String SERVICE_CREATE_LOAN = "createLoan";
    public static final String SERVICE_EXTEND_LOAN = "extendLoan";

    public LoanController() {
        super(Loan.class);        
    }

    @Autowired
    public void setLoanService(ILoanService loanService) {
        this.loanService = loanService;
        super.setService(loanService);
    }

    @Autowired
    public void setUserService(IUserService userService) {
        this.userService = userService;
    }

    @RequestMapping(method=RequestMethod.GET, value="/createLoan")
    public @ResponseBody String createLoan(@RequestParam final String amount,
            @RequestParam final String date,
            @RequestParam final Integer userId, HttpServletRequest request) {
        String ipAddr = "";
        ipAddr=request.getRemoteAddr();

        Loan loan = new Loan();
        // checks for input params
        StringBuilder error = new StringBuilder();
        if (amount == null || amount.length() < 1) {
            error.append(ERROR_AMOUNT_IS_NULL);
        }
        if (date == null || amount.length() < 1) {
            error.append(ERROR_DATE_IS_NULL);
        }
        try {
            loan.setAmount(Float.parseFloat(amount));
        } catch (Exception e) {
            error.append(ERROR_WRONG_AMOUNT_FORMAT);
        }
        try {
            loan.setDeadLine(FORMATTER.parse(date));
        } catch (Exception e) {
            error.append(ERROR_WRONG_DATE_FORMAT);
        }

        User user = userService.get(userId);
        if (user == null) {
            error.append(ERROR_WRONG_USER_ID);
        }

        // main check procedure
        error.append(loanService.checkLoan(ipAddr, loan.getAmount()));

        if (error.length() == 0) {
            loan.setRate(DEFAULT_RATE);
            loan.setIpAddr(ipAddr);
            loan.setState(Loan.STATE.OPEN.toString());
            loan.setCreateDate(new Date());
            loan.setUser(user);
            addEntity(loan);
            return RESULT_OK;
        } else {
            return RESULT_REJECTED + error.toString();
        }
    }
    
    @RequestMapping(method=RequestMethod.GET, value="/extendLoan")
    public @ResponseBody String extendloan(@RequestParam("id") final Integer id) throws JsonHelperException {
        String json=getEntity(id);
        Loan loan=(Loan) JsonHelper.Json2Object(json, Loan.class);
        Date now=new Date();
        if (loan.getDeadLine().getTime()<now.getTime()) {
            return ERROR_EXTEND_DEADLINE_EXCEED;
        }
        loanService.extendLoan(id);
        return RESULT_EXTENDED;
    }

}

6. Configuration

6.1. Main config

package com.demien.springmvcrest.config;

import java.util.Properties;

import org.hibernate.SessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import com.demien.springmvcrest.dao.BaseDAOImpl;
import com.demien.springmvcrest.dao.IBaseDAO;
import com.demien.springmvcrest.domain.Loan;
import com.demien.springmvcrest.domain.User;


@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.demien.springmvcrest")
public class AppConfig {

    // ---------- DAO ------------------
    @Bean
    public DriverManagerDataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        Properties p = new Properties();
        dataSource.setConnectionProperties(p);
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:~/test");
        dataSource.setUsername("sa");
        dataSource.setPassword("sa");
        return dataSource;
    }

    @SuppressWarnings("rawtypes")
    @Bean
    public LocalSessionFactoryBean sessionFactoryBean() {
        LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource());
        Class[] annotatedClasses = new Class[] { User.class, Loan.class };
        sessionFactoryBean.setAnnotatedClasses(annotatedClasses);
        Properties p = new Properties();
        p.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        p.put("hibernate.show_sql", "true");
        p.put("hibernate.hbm2ddl.auto", "create");
        sessionFactoryBean.setHibernateProperties(p);
        return sessionFactoryBean;
    }

    @Bean
    public SessionFactory sessionFactory() {
        return sessionFactoryBean().getObject();
    }

    @Bean
    public HibernateTransactionManager txManager() {
        HibernateTransactionManager txManager = new HibernateTransactionManager();
        txManager.setSessionFactory(sessionFactory());
        return txManager;
    }

    @Bean
    public TransactionAttributeSource annotationTransactionAttributeSource() {
        return new AnnotationTransactionAttributeSource();
    }

    @Bean
    public IBaseDAO<Loan> loanDAOImpl() {
        IBaseDAO<Loan> loanDAOImpl = new BaseDAOImpl<Loan>(Loan.class,
                sessionFactory());
        return loanDAOImpl;
    }

    @Bean
    public IBaseDAO<User> userDAOImpl() {
        IBaseDAO<User> userDAOImpl = new BaseDAOImpl<User>(User.class,
                sessionFactory());
        return userDAOImpl;
    }


}


6.2. Configuration for enabling MVC

package com.demien.springmvcrest.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;


@Configuration
@EnableWebMvc
@Profile("container")
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    
}


7. Utilities

7.1 JsonJelper - for converting Object->Json->Object

package com.demien.springmvcrest.utils;

import java.util.List;
import org.codehaus.jackson.map.ObjectMapper;

public class JsonHelper {
    
    private static ObjectMapper mapper = new ObjectMapper(); 
    
    @SuppressWarnings("rawtypes")
    public static Object Json2Object(String json, Class cl) throws JsonHelperException {    
        return Json2ObjectMain(json, cl, false);
    }   
    
    @SuppressWarnings("rawtypes")
    public static Object Json2ObjectList(String json, Class cl) throws  JsonHelperException{
        return Json2ObjectMain(json, cl, true);
    }
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private static Object Json2ObjectMain(String json, Class cl, boolean asList) throws JsonHelperException {
        if (json==null || json.length()==0) {
            return null;
        }
        Object result=null;
        try {
            if (asList) {
                result=mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, cl));
            } else {
                result=mapper.readValue(json, cl);  
            }
        } catch (Exception e) {
            throw new JsonHelper.JsonHelperException("Exception in Json2Object. Exception:"+e.getMessage()+". JSON="+json);
        }
        
        return result;
    }
    
    public static String object2json(Object object) throws JsonHelperException {
        String result="";
        try {
           result=mapper.writeValueAsString(object);
        } catch (Exception e){
            throw new JsonHelper.JsonHelperException("Exception in object2json. Exception:"+e.getMessage()+".");
        }
        return result;
    }
    
    public static class JsonHelperException extends Exception {
        
        private static final long serialVersionUID = -7568626836534766197L;

        public JsonHelperException(String message) {
            super(message);
        }
    }
}

7.2. Object data populator - for tests - fill object fields with random values

package com.demien.springmvcrest.utils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

import com.demien.springmvcrest.domain.IPersistable;


public class ObjectDataPopulator {

    public static Object populate(final IPersistable instance)
            throws Exception {
        instance.setId(getRandomInteger());
        List<Field> fields = getAllFields(instance);
        for (Field eachField : fields) {
            eachField.setAccessible(true);
            if (eachField.getType().equals(Integer.class)) {
                eachField.set(instance, getRandomInteger());
            } else if (eachField.getType().equals(String.class)) {
                eachField.set(instance, getRandomString());
            } else if (eachField.getType().equals(Float.class)) {
                eachField.set(instance, getRandomFloat());
            } else if (eachField.getType().equals(Date.class)) {
                eachField.set(instance, getRandomDate());   
            } else if (eachField.getType().equals(Set.class)) { 
             // here should be generators for other standard types like Float, Long....
            } else {
                // processing aggregated objects
                if (eachField.getType().newInstance() instanceof IPersistable) {
                    //eachField.set(instance, populate((IPersistable) eachField.getType().newInstance()));
                    
                }                   
            }
        }

        return instance;

    }

    private static Integer getRandomInteger() {
        return new Integer((int) (Math.random() * 1000));
    }
    
    private static Float getRandomFloat() {
        return new Float( Math.random() * 1000+Math.random());
    }
    
    private static Date getRandomDate() {
        return new Date(  new Date().getTime() - (int)(Math.random() * 1000*60*60*24*100) );
    }

    private static String getRandomString() {
        StringBuffer result = new StringBuffer();
        String[] letters = new String[] { "A", "B", "C", "D", "E", "F", "G" };
        int length = (int) (Math.random() * 15) + 5;
        for (int i = 0; i < length; i++) {
            int pos = (int) (Math.random() * letters.length);
            result.append(letters[pos]);
        }
        return result.toString();
    }

    private static List<Field> getAllFields(final Object instance) {
        Field[] fields = instance.getClass().getDeclaredFields();
        List<Field> result = new ArrayList<Field>();
        for (int i = 0; i < fields.length; i++) {
            if (!java.lang.reflect.Modifier.isFinal(fields[i].getModifiers())
                    && !java.lang.reflect.Modifier.isStatic(fields[i]
                            .getModifiers())) {
                result.add(fields[i]);
            }
        }
        return result;
    }
}


7.3. RestTestClient - client for calling rest methods and transform results into proper objects

package com.demien.springmvcrest.utils;

import java.io.BufferedReader;

import org.apache.http.NameValuePair;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;

import com.demien.springmvcrest.utils.JsonHelper.JsonHelperException;

public class RestTestClient {

    public static final int RESPONSE_OK = 200;

    public String getResponseData(HttpResponse response)
            throws Exception {
        StringBuilder result = new StringBuilder();
        try {
            BufferedReader rd = new BufferedReader(new InputStreamReader(
                    response.getEntity().getContent()));
            String line = "";
            while ((line = rd.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            throw new Exception("Exception in getResponseData:"
                    + e.getMessage() + ".");
        }
        return result.toString();
    }

    @SuppressWarnings("rawtypes")
    public Object Json2Object(String src, Class cl, boolean asList)
            throws JsonHelperException {
        Object result = null;
        if (asList) {
            result = JsonHelper.Json2ObjectList(src, cl);
        } else {
            result = JsonHelper.Json2Object(src, cl);
        }
        return result;
    }

    public HttpResponse sendGet(String url) throws ClientProtocolException,
            IOException {
        HttpClient client = HttpClientBuilder.create().build();
        HttpGet get = new HttpGet(url);
        get.setHeader(
                "Accept",
                "text/html,application/xhtml+xml,application/xml,application/json;q=0.9,*/*;q=0.8");
        get.setHeader("Content-Type", "application/json");
        HttpResponse response = client.execute(get);

        return response;
    }

    public String sendGetStringResult(String url) throws Exception {
        return getResponseData(sendGet(url));
    }

    @SuppressWarnings("rawtypes")
    public Object sendGetObjectResult(String url, Class cl) throws Exception {
        return sendGetObjectResult(url, cl, false);
    }

    @SuppressWarnings("rawtypes")
    public Object sendGetObjectResult(String url, Class cl, boolean asList)
            throws Exception {
        String src = sendGetStringResult(url);
        Object result = null;
        if (asList) {
            result = JsonHelper.Json2ObjectList(src, cl);
        } else {
            result = JsonHelper.Json2Object(src, cl);
        }
        return result;
    }
    
    public HttpResponse sendPost(String url, List<NameValuePair> postParams, Object object) throws Exception {
        HttpClient client = HttpClientBuilder.create().build();
        HttpPost post = new HttpPost(url);
        
        post.setHeader("Accept",
                "text/html,application/xhtml+xml,application/xml, application/json;q=0.9,*/*;q=0.8");
        
        if (postParams != null) {
            post.setEntity(new UrlEncodedFormEntity(postParams));
            post.setHeader("Content-Type", "application/x-www-form-urlencoded");
        }
        if (object!=null) {
            String json = JsonHelper.object2json(object);
            StringEntity input = new StringEntity(json);
            input.setContentType("application/json");
            post.setEntity(input);
            post.setHeader("Content-Type", "application/json");

        }

        HttpResponse response = client.execute(post);

        return response;        
    }

    public HttpResponse sendPost(String url, List<NameValuePair> postParams)
            throws Exception {
       return sendPost(url, postParams, null);
    }

    public HttpResponse sendPost(String url, Object object) throws Exception {
        return sendPost(url, null, object);
    }

    public String sendPostStringResult(String url,
            List<NameValuePair> postParams) throws IllegalStateException,
            IOException, Exception {
        return getResponseData(sendPost(url, postParams));
    }

    @SuppressWarnings("rawtypes")
    public Object sendPostObjectResult(String url,
            List<NameValuePair> postParams, Class cl)
            throws IllegalStateException, IOException, Exception {
        return sendPostObjectResult(url, postParams, cl, false);
    }

    @SuppressWarnings("rawtypes")
    public Object sendPostObjectResult(String url,
            List<NameValuePair> postParams, Class cl, boolean asArray)
            throws IllegalStateException, IOException, Exception {
        String src = sendPostStringResult(url, postParams);
        return Json2Object(src, cl, asArray);
    }

}

7.4. RestTestServer - starting application on embedded jetty server

package com.demien.springmvcrest.utils;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import java.io.IOException;

public class RestTestServer {

    public static final int TEST_PORT = 8080;
    public static final String TEST_CONTEXT = "/";
    private static final String CONFIG_LOCATION = "com.demien.springmvcrest.config";
    private static final String MAPPING_URL = "/*";
    private String mode;
    private Server server;

    public RestTestServer() {

    }

    public RestTestServer(final String mode) {
        this.mode = mode;
    }    

    public void start() throws Exception {
        //LOGGER.debug("Starting server at port :"+ TEST_PORT);
        server = new Server(TEST_PORT);
        server.setHandler(getServletContextHandler(getContext()));
        server.start();
        System.out.println("Server started at http://localhost:" + TEST_PORT + TEST_CONTEXT);
        //LOGGER.info("Server started at http://localhost:" + TEST_PORT + TEST_CONTEXT);
        if (mode == null || !mode.equals("JUNIT")) {
            server.join();
        }
     }
    
    public void stop() throws Exception {
        if (server != null) {
            server.stop();
            server.join();
            server.destroy();
            server = null;
        }
    }   

    private ServletContextHandler getServletContextHandler(WebApplicationContext context) throws IOException {
        ServletContextHandler contextHandler = new ServletContextHandler();
        contextHandler.setErrorHandler(null);
        contextHandler.setContextPath(TEST_CONTEXT);
        contextHandler.addServlet(new ServletHolder(new DispatcherServlet(context)), MAPPING_URL);
        contextHandler.addEventListener(new ContextLoaderListener(context));
        contextHandler.setResourceBase(new ClassPathResource("webapp").getURI().toString());
        return contextHandler;
    }

    private WebApplicationContext getContext() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        //context.register(AppConfig.class);
        context.setConfigLocation(CONFIG_LOCATION);
        return context;
    }

}


8. Unit tests  - there are a lot of tests in application - only few of them will be shown.

8.1. BaseDAOTest


package com.demien.springmvcrest.dao;

import java.io.Serializable;

import junit.framework.Assert;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.Before;
import org.junit.Test;

import com.demien.springmvcrest.dao.BaseDAOImpl;

import static org.mockito.Mockito.*;

public class BaseDAOTest {
    BaseDAOImpl<TestClass> dao;
    SessionFactory sessionFactory;
    Session session;
    TestClass testObject;
    
    private class TestClass implements Serializable {
        private static final long serialVersionUID = -8877252618686760948L;
    }
    
    @Before
    public void init() {
        testObject=new TestClass();
        
        sessionFactory=mock(SessionFactory.class);
        session=mock(Session.class);
        when(sessionFactory.getCurrentSession()).thenReturn(session);
        
        dao=new BaseDAOImpl<TestClass>(TestClass.class, sessionFactory);
    }
    
    @Test
    public void getTest() {
        Integer id=1;
        when(session.get(TestClass.class, id)).thenReturn(testObject);
        Object result=dao.get(id);
        Assert.assertEquals(testObject, result);
    }
    
    @Test
    public void saveTest() {
        TestClass newObject=new TestClass();
        when(session.save(testObject)).thenReturn(newObject);
        Object result=dao.save(testObject);
        Assert.assertEquals(newObject, result);
    }
    
    @Test
    public void updateTest() {
        dao.update(testObject);
        verify(session).update(testObject);
    }
    
    @Test
    public void deleteTest() {
        dao.delete(testObject);
        verify(session).delete(testObject);
    }
    
    @Test
    public void queryTest() {
        Query query=mock(Query.class);
        when(session.createQuery(anyString())).thenReturn(query);
        //1 
        dao.query("select * from dual", null);
        verify(query).list();
        //2 
        dao.query("delete from dual", null);
        verify(query).executeUpdate();   
    }
}

8.2. LoanController test

package com.demien.springmvcrest.rest;

import static junit.framework.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

import com.demien.springmvcrest.service.ILoanService;
import com.demien.springmvcrest.service.IUserService;
import com.demien.springmvcrest.service.LoanServiceImplTest;
import com.demien.springmvcrest.utils.JsonHelper.JsonHelperException;
import com.demien.springmvcrest.utils.ObjectDataPopulator;
import com.demien.springmvcrest.AppConst;
import com.demien.springmvcrest.controller.LoanController;
import com.demien.springmvcrest.domain.Loan;
import com.demien.springmvcrest.domain.User;

public class LoanControllerTest extends BaseControllerTest<Loan> {

    private static ILoanService loanService;
    private static IUserService userService;

    private LoanController controller;


    public LoanControllerTest() {
        super(Loan.class);
    }

    @Before
    public void init() {
        loanService=mock(ILoanService.class);
        userService=mock(IUserService.class);
        controller=new LoanController();
        controller.setLoanService(loanService);
        controller.setUserService(userService);
    }



    @Test
    public void createLoanTest () throws Exception {
        String ipAddr="127.0.0.1";
        HttpServletRequest request=mock(HttpServletRequest.class);
        when(request.getRemoteAddr()).thenReturn(ipAddr);
        
        User user=new User();
        ObjectDataPopulator.populate(user);
        Loan loan=new Loan();
        ObjectDataPopulator.populate(loan);
        when(userService.get(user.getId())).thenReturn(user);
        ArgumentCaptor<Loan> captor = ArgumentCaptor.forClass(Loan.class);
        String dateStr=AppConst.FORMATTER.format(loan.getDeadLine());
        when(loanService.checkLoan(ipAddr, loan.getAmount())).thenReturn("");
        controller.createLoan(loan.getAmount().toString(), dateStr, user.getId(), request);
        verify(userService).get(user.getId());
        verify(loanService).save(captor.capture());
        assertEquals(loan.getAmount(), captor.getValue().getAmount());
        assertEquals(dateStr, AppConst.FORMATTER.format(captor.getValue().getDeadLine()) );
        assertEquals(user.getId(), captor.getValue().getUser().getId());
    }

    @Test
    public void extendLoanTest() throws JsonHelperException {
        Integer id=666;
        
        Date d=new Date();
        Loan loan=LoanServiceImplTest.getTestLoan("", new Date());
        loan.setDeadLine(new Date(d.getTime()+AppConst.DAY_SECONDS));
        when(loanService.get(id)).thenReturn(loan);
        
        controller.extendloan(id);
        verify(loanService).extendLoan(id);
    }


}

9. Integration tests

9.1. Base Integration test - test for BaseController

package com.demien.springmvcrest.integration.test;

import static org.junit.Assert.*;

import java.io.IOException;
import java.util.List;

import org.apache.http.HttpResponse;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import com.demien.springmvcrest.controller.BaseController;
import com.demien.springmvcrest.utils.ObjectDataPopulator;
import com.demien.springmvcrest.utils.RestTestClient;
import com.demien.springmvcrest.utils.RestTestServer;
import com.demien.springmvcrest.AppConst;
import com.demien.springmvcrest.domain.IPersistable;

public abstract class BaseIT<T extends IPersistable> {
    protected static RestTestServer server = new RestTestServer("JUNIT");
    protected static RestTestClient client = new RestTestClient();

    protected final String BASE_URL = "http://localhost:"
            + RestTestServer.TEST_PORT + RestTestServer.TEST_CONTEXT
            //+ "/services/rest/"
            ;
    protected final String SERVICE_URL;
    private Class<T> cl;

    public BaseIT(String sevicePrexif, Class<T> cl) {
        SERVICE_URL = BASE_URL + sevicePrexif + "/";
        this.cl=cl;
    }

    @BeforeClass
    public static void init() throws Exception {
        server.start();
    }

    @AfterClass
    public static void finish() throws Exception {
        server.stop();
    }
    
    private T getTestEntity() throws Exception {
      T entity = cl.newInstance();
      ObjectDataPopulator.populate(entity);
    return entity;
   }
    
    @Test
    public void sayHello() throws Exception {
        String value = client.sendGetStringResult(SERVICE_URL
                + BaseController.SERVICE_HELLO);
        System.out.println("value=" + value);
        assertEquals(AppConst.HELLO, value);
    }

    @SuppressWarnings("unchecked")
    private T getById(Integer id) throws Exception {
        T result = (T) client.sendGetObjectResult(SERVICE_URL
                + BaseController.SERVICE_GETBYID + "?id=" + id, cl);
        return result;
    }

    private void addEntity(T entity) throws Exception {
        HttpResponse response = client.sendPost(SERVICE_URL
                + BaseController.SERVICE_ADD, entity);
        int responseCode = response.getStatusLine().getStatusCode();
        assertEquals(RestTestClient.RESPONSE_OK, responseCode);
    }

    private void updateEntity(T entity) throws Exception {
        HttpResponse response = client.sendPost(SERVICE_URL
                + BaseController.SERVICE_UPDATE, entity);
        int responseCode = response.getStatusLine().getStatusCode();
        assertEquals(RestTestClient.RESPONSE_OK, responseCode);
    }

    private void deleteEntity(T entity) throws Exception {
        HttpResponse response = client.sendPost(SERVICE_URL
                + BaseController.SERVICE_DELETE, entity);
        int responseCode = response.getStatusLine().getStatusCode();
        assertEquals(RestTestClient.RESPONSE_OK, responseCode);
    }

    @SuppressWarnings({ "unchecked" })
    private List<T> getEntityList() throws IllegalStateException, IOException,
            Exception {

        List<T> result = (List<T>) client.sendGetObjectResult(SERVICE_URL
                + BaseController.SERVICE_GET_ALL, cl, true);
        return result;
    }

    @Test
    public void addEntityTest() throws Exception {
        int cnt=getEntityList().size();
        T testEntity = getTestEntity();
        addEntity(testEntity);
        assertEquals(cnt+1, getEntityList().size());
    }

    @Test
    public void updateEntityTest() throws Exception {
        T testEntity = getTestEntity();
        addEntity(testEntity);
        testEntity=getEntityList().get(0);
        Integer id = testEntity.getId();

        T forUpdate = getTestEntity();
        forUpdate.setId(id);
        updateEntity(forUpdate);

        T searchEntity = getById(id);
        assertEquals(forUpdate, searchEntity);
    }

    @Test
    public void deleteEntityTest() throws Exception {
        T testEntity = getTestEntity();
        addEntity(testEntity);
        Integer id = getEntityList().get(0).getId();
        testEntity.setId(id);

        deleteEntity(testEntity);

        T searchEntity = getById(id);
        assertTrue(searchEntity==null);
    }
}


9.2. Loan integration test- for Loan Controller

package com.demien.springmvcrest.integration.test;

import static com.demien.springmvcrest.AppConst.*;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;

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

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.test.context.web.WebAppConfiguration;

import com.demien.springmvcrest.controller.BaseController;
import com.demien.springmvcrest.controller.LoanController;
import com.demien.springmvcrest.service.ILoanService;
import com.demien.springmvcrest.service.IUserService;
import com.demien.springmvcrest.utils.ObjectDataPopulator;
import com.demien.springmvcrest.config.AppConfig;
import com.demien.springmvcrest.config.WebMvcConfig;
import com.demien.springmvcrest.domain.Loan;
import com.demien.springmvcrest.domain.User;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfig.class,WebMvcConfig.class}, loader = AnnotationConfigContextLoader.class)
@WebAppConfiguration
public class LoanIT extends BaseIT<Loan> {

    @Autowired
    private ILoanService loanService;

    @Autowired
    private IUserService userService;

    @Autowired
    private LoanController loanController;
    
    private final int DAY_SECONDS=1000*60*60*24;

    public LoanIT() {
        super(LoanController.SERVICE_MAIN, Loan.class);
    }

    @Before
    public void clear() throws Exception {
        loanService.deleteAll();
        userService.deleteAll();
        // create test user
        User user=new User();
        ObjectDataPopulator.populate(user);
        userService.save(user);
    }

    private Integer getTestUserId() {
        User user=userService.getAll().get(0);
        return user.getId();
    }

    @SuppressWarnings("unchecked")
    private List<Loan> getAllLoans() throws Exception {
        List<Loan> loans = (List<Loan>) client.sendGetObjectResult(SERVICE_URL
                +  BaseController.SERVICE_GET_ALL, Loan.class, true);
        assertNotNull(loans);
        return loans;
    }

    private String createLoan(final Float amount, final Date date, final Integer userId) throws Exception {
        String result=client.sendGetStringResult(SERVICE_URL+LoanController.SERVICE_CREATE_LOAN+"?amount="+amount+"&date="+FORMATTER.format(date)+"&userId="+userId);
        assertNotNull(result);
        return result;
    }

    @Test
    public void loanCreationSuccessfullTest() throws Exception {
        List<Loan> loans =getAllLoans();
        int cnt=loans.size();
        assertEquals(0, cnt);
        // create
        String result=createLoan(1f, new Date(), getTestUserId());
        assertEquals(result, RESULT_OK);
        loans =getAllLoans();
        assertEquals(1, loans.size());
        //check for user
        Loan loan=loans.get(0);
        assertEquals(getTestUserId(), loan.getUser().getId());
    }

    @Test
    public void loanCreationRejectedByMaxIpRequestTest() throws Exception {
        // create 3 requests from one ip
        for (int i=0; i<CHECK_MAX_IP_DAY_REQUESTS; i++) {
            String result=createLoan(1f, new Date(), getTestUserId());
            assertEquals(result, RESULT_OK);
        }
        // next should fail
        String result=createLoan(1f, new Date(), -666);
        assertTrue(result.contains(RESULT_REJECTED));
        assertTrue(result.contains(REJECT_REASON_IP_DAY_ATTEMPTS));
        assertTrue(result.contains(ERROR_WRONG_USER_ID));
    }

    @SuppressWarnings("deprecation")
    @Test
    public void loanCreationRejectedByBadTimeAndMaxAmountTest() throws Exception {
        // set "wrong time"
        Date d=new Date();
        loanService.setCheckHourFrom(d.getHours()-1);
        loanService.setCheckHourTo(d.getHours()+1);

        String result=createLoan(CHECK_MAX_AMOUNT+1, new Date(), getTestUserId());

        assertTrue(result.contains(RESULT_REJECTED));
        assertTrue(result.contains(REJECT_REASON_BAD_TIME));
    }

    @Test
    public void testLoanExtendSuccess() throws Exception {
        Date d=new Date();
        String result=createLoan(1f, new Date(d.getTime()+DAY_SECONDS ), getTestUserId());              
        assertEquals(result, RESULT_OK);
        List<Loan> loans =getAllLoans();
        int cnt=loans.size();
        Loan loan=loans.get(0);
        result=client.sendGetStringResult(SERVICE_URL+LoanController.SERVICE_EXTEND_LOAN+"?id="+loan.getId());
        assertEquals(RESULT_EXTENDED, result);
        loans =getAllLoans();
        assertEquals(cnt+1, loans.size());
        // check for correct ID/PARENT ID
        int cnt1=0;
        int cnt2=0;
        for (Loan eachLoan:loans) {
            if (eachLoan.getId().equals(loan.getId())) {
                cnt1++;
                assertEquals(Loan.STATE.EXTENDED.toString(), eachLoan.getState());
            }
            if (eachLoan.getParentId()!=null && eachLoan.getParentId().equals(loan.getId())) {
                cnt2++;
                assertEquals(Loan.STATE.OPEN.toString(), eachLoan.getState());
                assertTrue(eachLoan.getRate()>loan.getRate());
                assertTrue(eachLoan.getDeadLine().getTime()>loan.getDeadLine().getTime());
            }
        }
        assertEquals(1,cnt1);
        assertEquals(1,cnt2);

    }
    
    @Test
    public void testLoanExtendFail() throws Exception {
        int DAY_SECONDS=1000*60*60*24;
        Date d=new Date();
        String result=createLoan(1f, new Date(d.getTime()-DAY_SECONDS), getTestUserId());
        assertEquals(result, RESULT_OK);
        Loan loan=getAllLoans().get(0);
        result=client.sendGetStringResult(SERVICE_URL+LoanController.SERVICE_EXTEND_LOAN+"?id="+loan.getId());      
        assertEquals(ERROR_EXTEND_DEADLINE_EXCEED, result);
        // check for correct ID/PARENT ID
    }    

}




10. resume

Commands for lunching application from command line:
mvn test   - run unit tests
mvn integration-test - run integration tests

Full source code can be downloaded from here