Part 6 – Adding AOP support

I heard a story about one senior (and quite highly paid) softwaree engineer. He was given task to log every method in every controller in project he was working on. Engineer rewrote all controller methods, so from code like this:

    @RequestMapping(method = RequestMethod.GET)
    public String showEmployees(Model model) {
        List<Employee> employees = employeeDao.list();
        model.addAttribute("employees", employees);

        return "employees/list";
    }

he made following code:

    @RequestMapping(method = RequestMethod.GET)
    public String showEmployees(Model model) {
	LOGGER.log("Invoking method showEmployees");

        List<Employee> employees = employeeDao.list();
        model.addAttribute("employees", employees);

	LOGGER.log("Returning from method showEmployees");
        return "employees/list";
    }

What’s wrong with this code? Well:

  • It takes lot of time to alter every method with such code
  • It is error prone – you can introduce typos or forget to add logging somewhere
  • It is mixing cross-cutting concerns. That means you are adding same kind of repetetive, boilerplate and unrelated code to places where it doesn’t belong.
  • For example, what is the responsibility of showEmployees method? It is invoking service, getting employees and putting them to model. Logging really isn’t it’s responsibility, so why to mix those concerns?

    If engineer I mentioned knew about Aspect Oriented Programming he would save lot of time and made code better and more readable. Spring supports something called “Aspects” that are made exactly for such a problems. Aspects allow us to define common functionality in one place. Before we write any code, there is some terminology to understand. This terminology is quite huge and I am not going to write it here, but I encourage you to read Spring’s official reference page on AOP if you wish to know more. You should at least understand what is Advice, Join Point, Pointcut, Aspect and Weaving.

    OK let’s add Aspect for logging controller methods, exactly what should’ve done engineer from the story in the beginning.

    We must first add dependencies to pom.xml on AspectJ library:

            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>1.6.11</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjtools</artifactId>
                <version>1.6.11</version>
            </dependency>
    

    Also check if you have dependency on Spring’s AOP (but if you follow this tutorial from the very beginning you already have it):

    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-aop</artifactId>
    			<version>3.1.0.RELEASE</version>
    		</dependency>
    

    Now let’s write Aspect’s code. Create package org.timesheet.aspects and add ControllerLoggingAspect class:

    package org.timesheet.aspects;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    
    import java.util.Arrays;
    
    /**
     * Will log every invokation of @RequestMapping annotated methods
     * in @Controller annotated beans.
     */
    @Aspect
    public class ControllerLoggingAspect {
    
        @Pointcut("within(@org.springframework.stereotype.Controller *)")
        public void controller() {}
    
        @Pointcut("execution(* *(..))")
        public void methodPointcut() {}
    
        @Pointcut("within(@org.springframework.web.bind.annotation.RequestMapping *)")
        public void requestMapping() {}
    
        @Before("controller() && methodPointcut() && requestMapping()")
        public void aroundControllerMethod(JoinPoint joinPoint) throws Throwable {
            System.out.println("Invoked: " + niceName(joinPoint));
        }
    
        @AfterReturning("controller() && methodPointcut() && requestMapping()")
        public void afterControllerMethod(JoinPoint joinPoint) {
            System.out.println("Finished: " + niceName(joinPoint));
        }
    
        private String niceName(JoinPoint joinPoint) {
            return joinPoint.getTarget().getClass()
                    + "#" + joinPoint.getSignature().getName()
                    + "\n\targs:" + Arrays.toString(joinPoint.getArgs());
        }
    
    }
    

    This code says, that @Before and @AfterReturning from controller method we will log information about it’s invokation (name and arguments). This advices execute when all three pointcuts are matching. controller() pointcut marks matching join point (that matches stereotype Controller) at which advice should be woven. methodPointcut() marks that we’re dealing with method call and requestMapping() pointcut marks methods annotated with @RequestMapping.

    To make it work, we’ll add aop.xml Spring configuration file under src/main/resources:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
    
        <!-- AOP support -->
        <bean id="controllerAspect" class="org.timesheet.aspects.ControllerLoggingAspect" />
        <aop:aspectj-autoproxy>
            <aop:include name="controllerAspect" />
        </aop:aspectj-autoproxy>
    
    </beans>
    

    And then we’ll import it in timesheet-servlet.xml Spring config:

    <import resource="classpath:aop.xml" />
    

    This was the last part of tutorial. I hope you have now better understanding of what Spring is and how does it help to solve your problems. Remember that we’ve covered only tiny piece of Spring in this tutorial. There is still much more to explore!

    Back to main tutorial page

Part 4 – Adding Spring MVC – part 1

Welcome to the fourth part of this tutorial. In this part, we will write controllers and views using Spring MVC and think about our REST model.

First thing that we must do, is make a web application from what we have so far. We will add web/WEB-INF folder to our project root. Inside WEB-INF create jsp folder. We will put our JSPs in that place. Inside that folder we will put deployment descriptor web.xml file with following contents:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee

http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"

         version="3.0">

    <display-name>timesheet-app</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:persistence-beans.xml
        </param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>timesheet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>timesheet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

Note that we are using servlet called timesheet. This is a dispatcher servlet. The following picture illustrates how Spring’s dispatcher servlet works (it’s called Front controller on picture below):

dispatcher servlet

  1. Request is handled by dispatcher servlet
  2. Dispatcher servlet decides to which controller it should deliver request (by request mapping, we’ll see that later) and then it delegates request
  3. Controller creates model and delivers it back to dispatcher servlet
  4. Dispatcher servlet resolves logical name of the view, binds model there and renders the view

The last step is quite mysterious. How does dispatcher servlet resolve logical name of the view? It uses something called ViewResolver. But we are not going to create our own by hand, instead we will just create another configuration file, define a bean with ViewResolver and have it injected by Spring. In WEB-INF create another Spring configuration file. By convention it must be named timesheet-servlet.xml, because we named our DispatcherServlet “timesheet” and this is file name, where Spring will be looking for config by default. Also create package org.timesheet.web. This is where we will be putting our controllers (which are again only annotated POJOs).

Here’s the timesheet-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <context:component-scan base-package="org.timesheet.web" />

    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

We defined prefix and suffix for resolving logical names. It is really simple. We calculate name like this:
full name = prefix + logical name + suffix
Therefore with our InternalResourceViewResolver, logical name “index” would be resolved to “/WEB-INF/jsp/index.jsp”.

For views we will be using JSP technology with JSTL (tag library), so we need to add another dependencies to our pom.xml file:

        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
        </dependency>

Now, we’d like to handle GET on /timesheet-app/welcome. So we need to write view and controller (for Model we’ll use one from Spring facilities). Let’s start with controller:

package org.timesheet.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.Date;

@Controller
@RequestMapping("/welcome")
public class WelcomeController {

    @RequestMapping(method = RequestMethod.GET)
    public String showMenu(Model model) {
        model.addAttribute("today", new Date());
        return "index";
    }

}

So when someone accesses url welcome (in our case http://localhost:8080/timesheet-app/welcome), this controller will handle request. We are also using Model and binding there value with name “today”. This is how we get value to view page.

Note that root of my application is /timesheet-app. It’s called application context. You can change that of course, but all remaining codes asume that you’re application context is set like that. If you’re deploying WAR it’s based on WAR’s name.

From showMenu method we’re returning “index” – which will be resolved to WEB-INF/jsp/index.jsp so let’s create such a page and put there some basic content:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>

<html>
<head>
    <title>Welcome to Timesheet app!</title>
</head>
<body>
    <h1>Welcome to the Timesheet App!</h1>

    <ul>
        <li><a href="managers">List managers</a></li>
        <li><a href="employees">List employees</a></li>
        <li><a href="tasks">List tasks</a></li>
        <li><a href="timesheets">List timesheets</a></li>
    </ul>

    <h2>Also check out <a href="timesheet-service">extra services!</a></h2>
    Today is: <fmt:formatDate value="${today}" pattern="dd-MM-yyyy" />
</body>
</html>

OK to recap, we added web.xml file, timesheet-servlet.xml Spring configuration file, controller class and jsp page. Let’s try to run this on some web container. I’ll be using Tomcat7, but if you feel more comfortable with another web container or even application server – feel free to switch. Now there are plenty of ways how to run Tomcat and how to deploy the application. You can:

Whichever you choose, be sure that you can access URL mentioned above before continuing. Honestly, deployment in Java is least fun for me, so don’t give up if you feel frustrated, it might not work for the first time. But with tutorials above, you probably won’t have any problems. Also don’t forget about setting right application context.

Before we write more controllers, let’s prepare some data. When Spring creates welcomeController bean, we’d like to have some data. So for now, let’s just write dummy generator that will just create some entites. Later in tutorial, we will see some more realistic solution.

Put helpers package under web package were controllers are place there EntityGenerator class:

package org.timesheet.web.helpers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.timesheet.domain.Employee;
import org.timesheet.domain.Manager;
import org.timesheet.domain.Task;
import org.timesheet.domain.Timesheet;
import org.timesheet.service.GenericDao;
import org.timesheet.service.dao.EmployeeDao;
import org.timesheet.service.dao.ManagerDao;
import org.timesheet.service.dao.TaskDao;
import org.timesheet.service.dao.TimesheetDao;

import java.util.List;

/**
 * Small util helper for generating entities to simulate real system.
 */
@Service
public final class EntityGenerator {

    @Autowired
    private EmployeeDao employeeDao;

    @Autowired
    private ManagerDao managerDao;

    @Autowired
    private TaskDao taskDao;

    @Autowired
    private TimesheetDao timesheetDao;

    public void generateDomain() {
        Employee steve = new Employee("Steve", "Design");
        Employee bill = new Employee("Bill", "Marketing");
        Employee linus = new Employee("Linus", "Programming");
        
        // free employees (no tasks/timesheets)
        Employee john = new Employee("John", "Beatles");
        Employee george = new Employee("George", "Beatles");
        Employee ringo = new Employee("Ringo", "Beatles");
        Employee paul = new Employee("Paul", "Beatles");

        Manager eric = new Manager("Eric");
        Manager larry = new Manager("Larry");
        
        // free managers
        Manager simon = new Manager("Simon");
        Manager garfunkel = new Manager("Garfunkel");

        addAll(employeeDao, steve, bill, linus, john, george, ringo, paul);
        addAll(managerDao, eric, larry, simon, garfunkel);

        Task springTask = new Task("Migration to Spring 3.1", eric, steve, linus);
        Task tomcatTask = new Task("Optimizing Tomcat", eric, bill);
        Task centosTask = new Task("Deploying to CentOS", larry, linus);

        addAll(taskDao, springTask, tomcatTask, centosTask);

        Timesheet linusOnSpring = new Timesheet(linus, springTask, 42);
        Timesheet billOnTomcat = new Timesheet(bill, tomcatTask, 30);

        addAll(timesheetDao, linusOnSpring, billOnTomcat);
    }
    
    public void deleteDomain() {
        List<Timesheet> timesheets = timesheetDao.list();
        for (Timesheet timesheet : timesheets) {
            timesheetDao.remove(timesheet);
        }

        List<Task> tasks = taskDao.list();
        for (Task task : tasks) {
            taskDao.remove(task);
        }

        List<Manager> managers = managerDao.list();
        for (Manager manager : managers) {
            managerDao.remove(manager);
        }

        List<Employee> employees = employeeDao.list();
        for (Employee employee : employees) {
            employeeDao.remove(employee);
        }
    }
    
    private <T> void addAll(GenericDao<T, Long> dao, T... entites) {
        for (T o : entites) {
            dao.add(o);
        }
    }
}

Now let’s use the code for WelcomeController. We’ll inject there generator and place special method annotated with @PostConstruct annotation. That’s JSR-250 annotation for bean lifecycle and Spring supports it. It means, that this method will be invoked right after Spring IoC container instantiates welcomeController bean.

package org.timesheet.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.timesheet.web.helpers.EntityGenerator;

import javax.annotation.PostConstruct;
import java.util.Date;

@Controller
@RequestMapping("/welcome")
public class WelcomeController {

    @Autowired
    private EntityGenerator entityGenerator;

    @RequestMapping(method = RequestMethod.GET)
    public String showMenu(Model model) {
        model.addAttribute("today", new Date());
        return "index";
    }

    @PostConstruct
    public void prepareFakeDomain() {
        entityGenerator.deleteDomain();
        entityGenerator.generateDomain();
    }
    
}

OK let’s write some controllers for domain logic now!

We’ll start by writing Employee’s controller. First, create class EmployeeController under org.timesheet.web package. Mark class as web controller and to handle “/employees” requests:

@Controller
@RequestMapping("/employees")
public class EmployeeController { ... 

For processing persistent data (Employees in this case) we need DAO and have it autowired by Spring’s IoC container, so let’s do just that:

    private EmployeeDao employeeDao;

    @Autowired
    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }

Now we want to handle HTTP GET method. When user accesses http://localhost:8080/timesheet-app/employees with a web browser, controller must handlet GET request. It’s only contacting DAO and collecting all employees and putting them into the model.

@RequestMapping(method = RequestMethod.GET)
    public String showEmployees(Model model) {
        List<Employee> employees = employeeDao.list();
        model.addAttribute("employees", employees);

        return "employees/list";
    }

Under jsp folder create employees folder where we will put all corresponding JSPs of employees. Probably you already noticed, that page with list of employees will be resolved to /WEB-INF/jsp/employees/list.jsp. So create such a page. We’ll see the contents later, if you want you can put there random text for now to see if it works.

In JSP page we will show a link next to employee for his personal page, which will look like http://localhost:8080/timesheet-app/employees/{id} where ID is employee’s id. This is RESTful URL because it’s resource oriented, and we’re directly identifying resource. RESTless URL would be something like http://localhost:8080/timesheet-app/employees.html?id=123. That’s action oriented and doesn’t identify resource.

Let’s add another method to controller, that handles this URL:

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String getEmployee(@PathVariable("id") long id, Model model) {
        Employee employee = employeeDao.find(id);
        model.addAttribute("employee", employee);

        return "employees/view";
    }

Again, create view.jsp page under /jsp/employees folder. From this page, we’d also like to alter employee. We’re simply access same URL but with different web method – POST. That means, we’re giving data from bounded model to update.

This method handles employee updating:

    @RequestMapping(value = "/{id}", method = RequestMethod.POST)
    public String updateEmployee(@PathVariable("id") long id, Employee employee) {
        employee.setId(id);
        employeeDao.update(employee);

        return "redirect:/employees";
    }

In this case, we were accessing employees/{id} with GET or POST method. But what if we want to delete employee? We’ll access same URL but with different method – DELETE. We will use additional business logic in EmployeeDao. If anything goes wrong, we’ll throw exception containing employee that cannot be deleted. So le’ts add controller method for this case:

    /**
     * Deletes employee with specified ID
     * @param id Employee's ID
     * @return redirects to employees if everything was ok
     * @throws EmployeeDeleteException When employee cannot be deleted
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public String deleteEmployee(@PathVariable("id") long id)
            throws EmployeeDeleteException {

        Employee toDelete = employeeDao.find(id);
        boolean wasDeleted = employeeDao.removeEmployee(toDelete);

        if (!wasDeleted) {
            throw new EmployeeDeleteException(toDelete);
        }

        // everything OK, see remaining employees
        return "redirect:/employees";
    }

Notice that we’re returning redirect from that method. The redirect: prefix signals that the request should be redirected to the path that it precedes.

Create package org.timesheet.web.exceptions and place following EmployeeDeleteException there:

package org.timesheet.web.exceptions;

import org.timesheet.domain.Employee;

/**
 * When employee cannot be deleted.
 */
public class EmployeeDeleteException extends Exception {

    private Employee employee;

    public EmployeeDeleteException(Employee employee) {
        this.employee = employee;
    }

    public Employee getEmployee() {
        return employee;
    }
}

Arguably, this exception could directly be thrown from DAO. Now how do we handle it? Spring has special annotation called @ExceptionHandler. We’ll place it in our controller and when specified exception is thrown, method annotated with ExceptionHandler will handle it and resolve correct view:

    /**
     * Handles EmployeeDeleteException
     * @param e Thrown exception with employee that couldn't be deleted
     * @return binds employee to model and returns employees/delete-error
     */
    @ExceptionHandler(EmployeeDeleteException.class)
    public ModelAndView handleDeleteException(EmployeeDeleteException e) {
        ModelMap model = new ModelMap();
        model.put("employee", e.getEmployee());
        return new ModelAndView("employees/delete-error", model);
    }

Okay, time for JSPs. We will be using some resources (like *.css or *.js) so in your web application root create resources folder. WEB-INF is NOT root, it’s the folder above. So resources and WEB-INF now should be in same level in directory tree. We’ve configured our dispatcher servlet to handle every request (with / url pattern), but we don’t want to have static resources handled by it atm. We’ll resolve that by simply putting mapping for default servlet in our web.xml file:

    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/resources/*</url-pattern>
    </servlet-mapping>

Under those resource create styles.css file. We’ll put CSS for whole app there right now even though we will use lotsa that stuff later.

table, th {
    margin: 10px;
    padding: 5px;
    width: 300px;
}

.main-table {
    border: 2px solid green;
    border-collapse: collapse;
}

.wide {
    width: 600px;
}

.main-table th {
    background-color: green;
    color: white;
}

.main-table td {
    border: 1px solid green;
}

th {
    text-align: left;
}

h1 {
    margin: 10px;
}

a {
    margin: 10px;
}

label {
    display: block;
    text-align: left;
}

#list {
    padding-left: 10px;
    position: relative;
}

#list ul {
    padding: 0;
}

#list li {
    list-style: none;
    margin-bottom: 1em;
}

.hidden {
    display: none;
}

.delete {
    margin: 0;
    text-align: center;
}

.delete-button {
    border: none;
    background: url('/timesheet-app/resources/delete.png') no-repeat top left;
    color: transparent;
    cursor: pointer;
    padding: 2px 8px;
}

.task-table {
    width: 150px;
    border: 1px solid #dcdcdc;
}

.errors {
    color: #000;
    background-color: #ffEEEE;
    border: 3px solid #ff0000;
    padding: 8px;
    margin: 16px;
}

Let’s now create employess/list.jsp page:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>

<html>
<head>
    <title>Employees</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h1>List of employees</h1>
    <a href="employees?new">Add new employee</a>
    <table cellspacing="5" class="main-table">
        <tr>
            <th>Name</th>
            <th>Department</th>
            <th>Details</th>
            <th>Delete</th>
        </tr>
        <c:forEach items="#{employees}" var="emp">
            <tr>
                <td>${emp.name}</td>
                <td>${emp.department}</td>
                <td>
                    <a href="employees/${emp.id}">Go to page</a>
                </td>
                <td>
                    <sf:form action="employees/${emp.id}" method="delete" cssClass="delete">
                        <input type="submit" class="delete-button" value="" />
                    </sf:form>
                </td>
            </tr>
        </c:forEach>
    </table>

    <br />
    <a href="welcome">Go back</a>
</body>
</html>

From that page, we’re linking our css under resources (with full name including application context). There’s also link linking to employee’s detail page (view.jsp) that’s resolved from employee’s id.
The most interesting part is usage of sf taglib. To stay Web 1.0 friendly, we unfortunatelly cannot directly use DELETE. Up to HTML4 and XHTML1, html forms can only use GET and POST. Workaround is to use hidden field that marks, if the POST actually should be used as DELETE. That’s precisely what Spring does for us for free – just using sf:form prefix. So we’re tunneling DELETE via HTTP POST, but it will be dispatched correctly.
To make this work, we must put in our web.xml special Spring’s filter for that:

    <filter>
        <filter-name>httpMethodFilter</filter-name>
        <filter-class>
            org.springframework.web.filter.HiddenHttpMethodFilter
        </filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>httpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

Even though that JSP is java specific technology that is actually compiled to servlet, we can use it pretty much like any HTML page. We’ve added some CSS and now we add most popular javascript library – jQuery. Go to https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js and download jquery.js file and drop it to your resources folder. We will allow users to update resource with POST, so we will use jQuery for some DOM manipulations – just for the sake of fancyness. You can use pretty much anything you can in common HTML pages.

Let’s now create /employees/view.jsp – that’s kinda detail page for employee.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>

<html>
<head>
    <title>Employee page</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h2>Employee info</h2>
    <div id="list">
        <sf:form method="post">
            <ul>
                <li>
                    <label for="name">Name:</label>
                    <input name="name" id="name" value="${employee.name}" disabled="true"/>
                </li>
                <li>
                    <label for="department">Department:</label>
                    <input name="department" id="department" value="${employee.department}" disabled="true" />
                </li>
                <li>
                    <input type="button" value="Unlock" id="unlock" />
                    <input type="submit" value="Save" id="save" class="hidden" />
                </li>
            </ul>
        </sf:form>
    </div>

    <br /><br />
    <a href="../employees">Go Back</a>

    <script src="/timesheet-app/resources/jquery-1.7.1.js"></script>
    <script>
        (function() {
            $("#unlock").on("click", function() {
                $("#unlock").addClass("hidden");

                // enable stuff
                $("#name").removeAttr("disabled");
                $("#department").removeAttr("disabled");
                $("#save").removeClass("hidden");
            });
        })();
    </script>
</body>
</html>

Inside the page we refer to jQuery file and we have self-invoking annonymous function – after button with id “unlock” is clicked, we hide it, bring forward the submit button and unlock fields so employee can be updated. After Save button is pressed, we’re redirected back to list of employees and this one has been updated.

The last functionality that we’re gonna get done for CRUD on Employee is adding. We will handle that by accessing employees with GET and extra prameter that we’ll call new. So URL for adding employee will be: http://localhost:8080/timesheet-app/employees?new
Let’s modify our controller for this:

    @RequestMapping(params = "new", method = RequestMethod.GET)
    public String createEmployeeForm(Model model) {
        model.addAttribute("employee", new Employee());
        return "employees/new";
    }

That will serve new JSP page – /WEB-INF/jsp/employees/new.jsp:

<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Add new employee</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h2>Add new Employee</h2>
    <div id="list">
        <sf:form method="post" action="employees">
            <ul>
                <li>
                    <label for="name">Name:</label>
                    <input name="name" id="name" value="${employee.name}"/>
                </li>
                <li>
                    <label for="department">Department:</label>
                    <input name="department" id="department"
                           value="${employee.department}" />
                </li>
                <li>
                    <input type="submit" value="Save" id="save" />
                </li>
            </ul>
        </sf:form>
    </div>

    <br /><br />
    <a href="employees">Go Back</a>
</body>
</html>

The page is pretty similar to view.jsp. In real world applications we’d use somerhing like Apache Tiles for reducing redundant code, but now let’s not worry about that.

Note that we submit form with “employees” action. Back to our controller, let’s handle employees with POST http method:

    @RequestMapping(method = RequestMethod.POST)
    public String addEmployee(Employee employee) {
        employeeDao.add(employee);

        return "redirect:/employees";
    }

And let’s not forget about error JSP page, when we cannot delete employee, jsp/employees/delete-error.jsp:

<html>
<head>
    <title>Cannot delete employee</title>
</head>
<body>
    Oops! Resource <a href="${employee.id}">${employee.name}</a> can not be deleted.

    <p>
        Make sure employee doesn't have assigned any task or active timesheet.
    </p>

    <br /><br /><br />
    <a href="../welcome">Back to main page.</a>
</body>
</html>

That’s it, we have whole CRUD functionality for our employees. Let’s recap the basic steps what we just did:

  • Added EmployeeController class
  • Create resources folder in web root for static content
  • Added mapping for default servlet in web.xml
  • Added styles.css to resources folder
  • Configured POST-DELETE tunneling with filter in web.xml
  • Downloaded jQuery.js and added to our resources folder
  • Added employess/list.jsp page
  • Added employess/view.jsp page
  • Added employess/new.jsp page
  • Added employees/delete-error.jsp page

Now, here’s complete code for the controller:

package org.timesheet.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ExceptionHandler;
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 org.timesheet.domain.Employee;
import org.timesheet.service.dao.EmployeeDao;
import org.timesheet.web.exceptions.EmployeeDeleteException;

import java.util.List;

/**
 * Controller for handling Employees.
 */
@Controller
@RequestMapping("/employees")
public class EmployeeController {

    private EmployeeDao employeeDao;

    @Autowired
    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    /**
     * Retrieves employees, puts them in the model and returns corresponding view
     * @param model Model to put employees to
     * @return employees/list
     */
    @RequestMapping(method = RequestMethod.GET)
    public String showEmployees(Model model) {
        List<Employee> employees = employeeDao.list();
        model.addAttribute("employees", employees);

        return "employees/list";
    }

    /**
     * Deletes employee with specified ID
     * @param id Employee's ID
     * @return redirects to employees if everything was ok
     * @throws EmployeeDeleteException When employee cannot be deleted
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public String deleteEmployee(@PathVariable("id") long id)
            throws EmployeeDeleteException {

        Employee toDelete = employeeDao.find(id);
        boolean wasDeleted = employeeDao.removeEmployee(toDelete);

        if (!wasDeleted) {
            throw new EmployeeDeleteException(toDelete);
        }

        // everything OK, see remaining employees
        return "redirect:/employees";
    }

    /**
     * Handles EmployeeDeleteException
     * @param e Thrown exception with employee that couldn't be deleted
     * @return binds employee to model and returns employees/delete-error
     */
    @ExceptionHandler(EmployeeDeleteException.class)
    public ModelAndView handleDeleteException(EmployeeDeleteException e) {
        ModelMap model = new ModelMap();
        model.put("employee", e.getEmployee());
        return new ModelAndView("employees/delete-error", model);
    }

    /**
     * Returns employee with specified ID
     * @param id Employee's ID
     * @param model Model to put employee to
     * @return employees/view
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String getEmployee(@PathVariable("id") long id, Model model) {
        Employee employee = employeeDao.find(id);
        model.addAttribute("employee", employee);

        return "employees/view";
    }

    /**
     * Updates employee with specified ID
     * @param id Employee's ID
     * @param employee Employee to update (bounded from HTML form)
     * @return redirects to employees
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.POST)
    public String updateEmployee(@PathVariable("id") long id, Employee employee) {
        employee.setId(id);
        employeeDao.update(employee);

        return "redirect:/employees";
    }

    /**
     * Creates form for new employee
     * @param model Model to bind to HTML form
     * @return employees/new
     */
    @RequestMapping(params = "new", method = RequestMethod.GET)
    public String createEmployeeForm(Model model) {
        model.addAttribute("employee", new Employee());
        return "employees/new";
    }

    /**
     * Saves new employee to the database
     * @param employee Employee to save
     * @return redirects to employees
     */
    @RequestMapping(method = RequestMethod.POST)
    public String addEmployee(Employee employee) {
        employeeDao.add(employee);

        return "redirect:/employees";
    }
    
}

If you’re using SpringSource Tool Suite, you can check mappings directly in IDE. Add to your project “Spring Project Nature”, in Properties->Spring->Bean Support configure your Spring’s config file. Then right click project and press Spring Tools->Show Request Mappings and you should see something like this:

Last thing about employees that remains is writing JUnit test. Since we have our timesheet-servlet.xml in WEB-INF, we can’t access its beans in JUnit test. What we’ll do is remove following line from timesheet-servlet.xml:

<context:component-scan base-package="org.timesheet.web" />

We now create new Spring’s bean config in src/main/resources and call it controllers.xml. The only thing we care about is to put autoscanning for controllers here, so content is pretty simple:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <context:component-scan base-package="org.timesheet.web" />

</beans>

To make context aware of those spring beans, alter context-param in web.xml like this:

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:persistence-beans.xml
            classpath:controllers.xml
        </param-value>
    </context-param>

Also, we now must import beans from controllers.xml to timesheet-servlet.xml, so instead of removed line <context:component-scan … from timesheet-servlet.xml, we add following:

<import resource="classpath:controllers.xml" />

That’ll allow us to have controllers autowired to tests. Okay, so in the test source folder create package org.timesheet.web and let’s put there EmployeeControllerTest. It’s pretty straightforward and we only test controller as POJO and how does it affect persistence layer (verifying via DAO). We made one exception however. In method testDeleteEmployeeThrowsException, we will explicitelly tell DAO to return false when trying to delete employee. This will save us complicated object creation and injection of additional DAOs. We will use popular mocking framework for this – Mockito.

Add dependency to your pom.xml:

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

Test for EmployeeController:

package org.timesheet.web;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import org.timesheet.DomainAwareBase;
import org.timesheet.domain.Employee;
import org.timesheet.service.dao.EmployeeDao;
import org.timesheet.web.exceptions.EmployeeDeleteException;

import java.util.Collection;
import java.util.List;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ContextConfiguration(locations = {"/persistence-beans.xml", "/controllers.xml"})
public class EmployeeControllerTest extends DomainAwareBase {
    
    @Autowired
    private EmployeeDao employeeDao;

    @Autowired
    private EmployeeController controller;
    
    private Model model; // used for controller
    
    @Before
    public void setUp() {
        model = new ExtendedModelMap();
    }

    @After
    public void cleanUp() {
        List<Employee> employees = employeeDao.list();
        for (Employee employee : employees) {
            employeeDao.remove(employee);
        }
    }

    @Test
    public void testShowEmployees() {
        // prepare some data
        Employee employee = new Employee("Lucky", "Strike");
        employeeDao.add(employee);

        // use controller
        String view = controller.showEmployees(model);
        assertEquals("employees/list", view);

        List<Employee> listFromDao = employeeDao.list();
        Collection<?> listFromModel = (Collection<?>) model.asMap().get("employees");

        assertTrue(listFromModel.contains(employee));
        assertTrue(listFromDao.containsAll(listFromModel));
    }
    
    @Test
    public void testDeleteEmployeeOk() throws EmployeeDeleteException {
        // prepare ID to delete
        Employee john = new Employee("John Lennon", "Singing");
        employeeDao.add(john);
        long id = john.getId();

        // delete & assert
        String view = controller.deleteEmployee(id);
        assertEquals("redirect:/employees", view);
        assertNull(employeeDao.find(id));
    }

    @Test(expected = EmployeeDeleteException.class)
    public void testDeleteEmployeeThrowsException() throws EmployeeDeleteException {
        // prepare ID to delete
        Employee john = new Employee("John Lennon", "Singing");
        employeeDao.add(john);
        long id = john.getId();

        // mock DAO for this call
        EmployeeDao mockedDao = mock(EmployeeDao.class);
        when(mockedDao.removeEmployee(john)).thenReturn(false);

        EmployeeDao originalDao = controller.getEmployeeDao();
        try {
            // delete & expect exception
            controller.setEmployeeDao(mockedDao);
            controller.deleteEmployee(id);
        } finally {
            controller.setEmployeeDao(originalDao);
        }
    }

    @Test
    public void testHandleDeleteException() {
        Employee john = new Employee("John Lennon", "Singing");
        EmployeeDeleteException e = new EmployeeDeleteException(john);
        ModelAndView modelAndView = controller.handleDeleteException(e);

        assertEquals("employees/delete-error", modelAndView.getViewName());
        assertTrue(modelAndView.getModelMap().containsValue(john));
    }
    
    @Test
    public void testGetEmployee() {
        // prepare employee
        Employee george = new Employee("George Harrison", "Singing");
        employeeDao.add(george);
        long id = george.getId();
        
        // get & assert
        String view = controller.getEmployee(id, model);
        assertEquals("employees/view", view);
        assertEquals(george, model.asMap().get("employee"));
    }

    @Test
    public void testUpdateEmployee() {
        // prepare employee
        Employee ringo = new Employee("Ringo Starr", "Singing");
        employeeDao.add(ringo);
        long id = ringo.getId();

        // user alters Employee in HTML form
        ringo.setDepartment("Drums");

        // update & assert
        String view = controller.updateEmployee(id, ringo);
        assertEquals("redirect:/employees", view);
        assertEquals("Drums", employeeDao.find(id).getDepartment());
    }

    @Test
    public void testAddEmployee() {
        // prepare employee
        Employee paul = new Employee("Paul McCartney", "Singing");
        
        // save but via controller
        String view = controller.addEmployee(paul);
        assertEquals("redirect:/employees", view);

        // employee is stored in DB
        assertEquals(paul, employeeDao.find(paul.getId()));
    }
}

Notice how do we use mocked dao for setting it in try/finally block. It’s just for that one call to ensure that correct exception is thrown. If you’ve never seen mocking, I definitelly suggest learning more about this technique. There are plenty of mocking frameworks. The one we picked – Mockito – comes with really neat syntax that heavily uses java static imports.

Now, Managers are pretty similar to Employees, so without any big problems, let’s add pretty similar stuff for managers:

First, create managers folder in WEB-INF/jsp.

Now let’s write controller and inject corresponding DAO:

@Controller
@RequestMapping("/managers")
public class ManagerController {

    private ManagerDao managerDao;

    @Autowired
    public void setManagerDao(ManagerDao managerDao) {
        this.managerDao = managerDao;
    }

    public ManagerDao getManagerDao() {
        return managerDao;
    }
}

Add method for listing managers:

    /**
     * Retrieves managers, puts them in the model and returns corresponding view
     * @param model Model to put employees to
     * @return managers/list
     */
    @RequestMapping(method = RequestMethod.GET)
    public String showManagers(Model model) {
        List<Manager> employees = managerDao.list();
        model.addAttribute("managers", employees);

        return "managers/list";
    }

Add list.jsp to jsp/managers:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>

<html>
<head>
    <title>Managers</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h1>List of managers</h1>
    <a href="managers?new">Add new manager</a>
    <table cellspacing="5" class="main-table">
        <tr>
            <th>Name</th>
            <th>Details</th>
            <th>Delete</th>
        </tr>
        <c:forEach items="#{managers}" var="man">
            <tr>
                <td>${man.name}</td>
                <td>
                    <a href="managers/${man.id}">Go to page</a>
                </td>
                <td>
                    <sf:form action="managers/${man.id}" method="delete" cssClass="delete">
                        <input type="submit" value="" class="delete-button" />
                    </sf:form>
                </td>
            </tr>
        </c:forEach>
    </table>

    <br />
    <a href="welcome">Go back</a>
</body>
</html>

Add method for deleting managers:

    /**
     * Deletes manager with specified ID
     * @param id Manager's ID
     * @return redirects to managers if everything was OK
     * @throws ManagerDeleteException When manager cannot be deleted
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public String deleteManager(@PathVariable("id") long id)
            throws ManagerDeleteException {

        Manager toDelete = managerDao.find(id);
        boolean wasDeleted = managerDao.removeManager(toDelete);

        if (!wasDeleted) {
            throw new ManagerDeleteException(toDelete);
        }

        // everything OK, see remaining managers
        return "redirect:/managers";
    }

Exception when deleting fails:

package org.timesheet.web.exceptions;

import org.timesheet.domain.Manager;

/**
 * When manager cannot be deleted
 */
public class ManagerDeleteException extends Exception {

    private Manager manager;

    public ManagerDeleteException(Manager manager) {
        this.manager = manager;
    }

    public Manager getManager() {
        return manager;
    }
}

Method for handling this exception:

    /**
     * Handles ManagerDeleteException
     * @param e Thrown exception with manager that couldn't be deleted
     * @return binds manager to model and returns managers/delete-error
     */
    @ExceptionHandler(ManagerDeleteException.class)
    public ModelAndView handleDeleteException(ManagerDeleteException e) {
        ModelMap model = new ModelMap();
        model.put("manager", e.getManager());
        return new ModelAndView("managers/delete-error", model);
    }

Add method for getting manager’s page:

    /**
     * Returns manager with specified ID
     * @param id Managers's ID
     * @param model Model to put manager to
     * @return managers/view
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String getManager(@PathVariable("id") long id, Model model) {
        Manager manager = managerDao.find(id);
        model.addAttribute("manager", manager);

        return "managers/view";
    }

Add manager’s page view.jsp under jsp/managers:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>

<html>
<head>
    <title>Manager page</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h2>Manager info</h2>
    <div id="list">
        <sf:form method="post">
            <ul>
                <li>
                    <label for="name">Name:</label>
                    <input name="name" id="name" value="${manager.name}" disabled="true"/>
                </li>
                <li>
                    <input type="button" value="Unlock" id="unlock" />
                    <input type="submit" value="Save" id="save" class="hidden" />
                </li>
            </ul>
        </sf:form>
    </div>

    <br /><br />
    <a href="../managers">Go Back</a>

    <script src="/timesheet-app/resources/jquery-1.7.1.js"></script>
    <script>
        (function() {
            $("#unlock").on("click", function() {
                $("#unlock").addClass("hidden");

                // enable stuff
                $("#name").removeAttr("disabled");
                $("#save").removeClass("hidden");
            });
        })();
    </script>
</body>
</html>

JSP page for handling error on deleting:

<html>
<head>
    <title>Cannot delete manager</title>
</head>
<body>
    Oops! Resource <a href="${manager.id}">${manager.name}</a> can not be deleted.

    <p>
        Make sure manager doesn't have assigned any task or active timesheet.
    </p>

    <br /><br /><br />
    <a href="../welcome">Back to main page.</a>
</body>
</html>

Add method for updating manager:

    /**
     * Updates manager with specified ID
     * @param id Manager's ID
     * @param manager Manager to update (bounded from HTML form)
     * @return redirects to managers
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.POST)
    public String updateManager(@PathVariable("id") long id, Manager manager) {
        manager.setId(id);
        managerDao.update(manager);

        return "redirect:/managers";
    }

Add method for returning new manager’s form:

    /**
     * Creates form for new manager
     * @param model Model to bind to HTML form
     * @return manager/new
     */
    @RequestMapping(params = "new", method = RequestMethod.GET)
    public String createManagerForm(Model model) {
        model.addAttribute("manager", new Manager());
        return "managers/new";
    }

Add page for new manager new.jsp under jsp/managers:

<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Add new manager</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h2>Add new Manager</h2>
    <div id="list">
        <sf:form method="post" action="managers">
            <ul>
                <li>
                    <label for="name">Name:</label>
                    <input name="name" id="name" value="${manager.name}"/>
                </li>
                <li>
                    <input type="submit" value="Save" id="save" />
                </li>
            </ul>
        </sf:form>
    </div>

    <br /><br />
    <a href="managers">Go Back</a>
</body>
</html>

And finally, add method for adding manager:

    /**
     * Saves new manager to the database
     * @param manager Manager to save
     * @return redirects to managers
     */
    @RequestMapping(method = RequestMethod.POST)
    public String addManager(Manager manager) {
        managerDao.add(manager);

        return "redirect:/managers";
    }

Ok last piece of code for this part is test case for ManagerController:

package org.timesheet.web;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import org.timesheet.DomainAwareBase;
import org.timesheet.domain.Manager;
import org.timesheet.service.dao.ManagerDao;
import org.timesheet.web.exceptions.ManagerDeleteException;

import java.util.Collection;
import java.util.List;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ContextConfiguration(locations = {"/persistence-beans.xml", "/controllers.xml"})
public class ManagerControllerTest extends DomainAwareBase {
    
    @Autowired
    private ManagerDao managerDao;

    @Autowired
    private ManagerController controller;
    
    private Model model; // used for controller
    
    @Before
    public void setUp() {
        model = new ExtendedModelMap();
    }

    @After
    public void cleanUp() {
        List<Manager> managers = managerDao.list();
        for (Manager manager : managers) {
            managerDao.remove(manager);
        }
    }

    @Test
    public void testShowManagers() {
        // prepare some data
        Manager manager = new Manager("Bob Dylan");
        managerDao.add(manager);

        // use controller
        String view = controller.showManagers(model);
        assertEquals("managers/list", view);

        List<Manager> listFromDao = managerDao.list();
        Collection<?> listFromModel = (Collection<?>) model.asMap().get("managers");

        assertTrue(listFromModel.contains(manager));
        assertTrue(listFromDao.containsAll(listFromModel));
    }
    
    @Test
    public void testDeleteManagerOk() throws ManagerDeleteException {
        // prepare ID to delete
        Manager john = new Manager("John Lennon");
        managerDao.add(john);
        long id = john.getId();

        // delete & assert
        String view = controller.deleteManager(id);
        assertEquals("redirect:/managers", view);
        assertNull(managerDao.find(id));
    }

    @Test(expected = ManagerDeleteException.class)
    public void testDeleteManagerThrowsException() throws ManagerDeleteException {
        // prepare ID to delete
        Manager john = new Manager("John Lennon");
        managerDao.add(john);
        long id = john.getId();

        // mock DAO for this call
        ManagerDao mockedDao = mock(ManagerDao.class);
        when(mockedDao.removeManager(john)).thenReturn(false);

        ManagerDao originalDao = controller.getManagerDao();
        try {
            // delete & expect exception
            controller.setManagerDao(mockedDao);
            controller.deleteManager(id);
        } finally {
            controller.setManagerDao(originalDao);
        }
    }

    @Test
    public void testHandleDeleteException() {
        Manager john = new Manager("John Lennon");
        ManagerDeleteException e = new ManagerDeleteException(john);
        ModelAndView modelAndView = controller.handleDeleteException(e);

        assertEquals("managers/delete-error", modelAndView.getViewName());
        assertTrue(modelAndView.getModelMap().containsValue(john));
    }

    @Test
    public void testGetManager() {
        // prepare manager
        Manager george = new Manager("George Harrison");
        managerDao.add(george);
        long id = george.getId();
        
        // get & assert
        String view = controller.getManager(id, model);
        assertEquals("managers/view", view);
        assertEquals(george, model.asMap().get("manager"));
    }

    @Test
    public void testUpdateManager() {
        // prepare manager
        Manager ringo = new Manager("Ringo Starr");
        managerDao.add(ringo);
        long id = ringo.getId();

        // user alters manager in HTML form
        ringo.setName("Rango Starr");

        // update & assert
        String view = controller.updateManager(id, ringo);
        assertEquals("redirect:/managers", view);
        assertEquals("Rango Starr", managerDao.find(id).getName());
    }

    @Test
    public void testAddManager() {
        // prepare manager
        Manager paul = new Manager("Paul McCartney");
        
        // save but via controller
        String view = controller.addManager(paul);
        assertEquals("redirect:/managers", view);

        // manager is stored in DB
        assertEquals(paul, managerDao.find(paul.getId()));
    }
}

Request mappings looks like this now:

So in this part, we learned what is Spring MVC, how to use our entities as models, how to write controllers in POJO style, how RESTful design looks like, how to create views with JSPs and how to setup application for using CSS and JavaScript.

We wrote controllers for Employees and Managers. In the next part we’ll continue with writing controllers for Tasks and Timesheets. Make sure everything works well so far before you proceed to the next part.

Here’s src folder (only new stuff is expanded. Don’t worry about .iml files, they’re for IntelliJ):

Here’s web folder:



Back to main tutorial page

Part 3 – DAO and Service layer

Welcome to the third part of Spring tutorial. In this part, we will continue in writing our Timesheet application and this time we’ll implement DAO layer, business services and write some tests.

In the previous part, we’ve defined GenericDao interface that tells us, what operations we will need to perform upon entities. Now we need to provide implementation. We will write class that performs these operations generically with Hibernate’s facilities (using SessionFactory). Therefore, any provided DAO automatically inherits these basic operations. We’ll talk about this later.

package org.timesheet.service.impl;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.timesheet.service.GenericDao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

/**
 * Basic DAO operations dependent with Hibernate's specific classes
 * @see SessionFactory
 */
@Transactional(propagation= Propagation.REQUIRED, readOnly=false)
public class HibernateDao<E, K extends Serializable> implements GenericDao<E, K> {

    private SessionFactory sessionFactory;
    protected Class<? extends E> daoType;

    public HibernateDao() {
        daoType = (Class<E>) ((ParameterizedType) getClass().getGenericSuperclass())
                        .getActualTypeArguments()[0];
    }

    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    protected Session currentSession() {
        return sessionFactory.getCurrentSession();
    }

    @Override
    public void add(E entity) {
        currentSession().save(entity);
    }

    @Override
    public void update(E entity) {
        currentSession().saveOrUpdate(entity);
    }

    @Override
    public void remove(E entity) {
        currentSession().delete(entity);
    }

    @Override
    public E find(K key) {
        return (E) currentSession().get(daoType, key);
    }

    @Override
    public List<E> list() {
        return currentSession().createCriteria(daoType).list();
    }
}

I want you to note couple of things about this code:

  • We’re using @Transcational annotation at the top of the class. That basically means, that DAO methods will run within transcations. To make it work, we need to alter our persistence-beans.xml file and declare there transaction manager, which will be handling the transactions. Just add following lines (new bean definition):
        <bean id="transactionManager"
              class="org.springframework.orm.hibernate3.HibernateTransactionManager">
            <property name="sessionFactory" ref="sessionFactory" />
        </bean>
    
  • We’re autowiring (@Autowired) SessionFactory using setter injection. As you should know, there are more kinds of injection (field, setter, constructor). Field injection looks best with Spring, because annotation resides directly at field, not on constructor or setter method. On the other hand, field injection is most useless one, because we cannot manually set other dependencies to private fields (for example in unit test). I prefer constructor injection whenever I can, because I don’t have to use mutator (setter) for the dependency. Objects are therefore constructed in more safe way. In this specific case we will use setter injection, because we’re designing this class for extension. If we’d pick constructor injection, all extending classes would have to have constructor matching one from the superclass.

    If you want to learn more about this, I recommend this brilliant book written by Dhanji R. Prasanna.
    Also note, that first line of constructor is doing some reflective magic. That’s because Java doesn’t have generics at runtime, only at compile time, so it prevents us from writing something like E.class. Therefore we used this ugly hack.

Now we have some basic template for DAO operations. In the real systems, there usually is DAO for each entity. That’s because sometimes those inherited CRUD operations are not enough, and you need some additional business operations. We will define interfaces (sets of operations for each DAO) that are typesafe and we will only depend on those later in controllers. We will implement them with Hibernate and have them autowired. Create new package org.timesheet.service.dao and add there following interfaces – DAOs for each entity:

package org.timesheet.service.dao;

import org.timesheet.domain.Employee;
import org.timesheet.service.GenericDao;

/**
 * DAO of employee.
 */
public interface EmployeeDao extends GenericDao<Employee, Long> {

    /**
     * Tries to remove employee from the system.
     * @param employee Employee to remove
     * @return {@code true} if employee is not assigned to any task
     * or timesheet. Else {@code false}.
     */
    boolean removeEmployee(Employee employee);

}
package org.timesheet.service.dao;

import org.timesheet.domain.Manager;
import org.timesheet.service.GenericDao;

/**
 * DAO of Manager.
 */
public interface ManagerDao extends GenericDao<Manager, Long> {
    /**
     * Tries to remove manager from the system.
     * @param manager Manager to remove
     * @return {@code true} if manager is not assigned to any task.
     * Else {@code false}.
     */
    boolean removeManager(Manager manager);
}
package org.timesheet.service.dao;

import org.timesheet.domain.Task;
import org.timesheet.service.GenericDao;

/**
 * DAO of Task.
 */
public interface TaskDao extends GenericDao<Task, Long> {

    /**
     * Tries to remove task from the system.
     * @param task Task to remove
     * @return {@code true} if there is no timesheet created on task.
     * Else {@code false}.
     */
    boolean removeTask(Task task);

}
package org.timesheet.service.dao;

import org.timesheet.domain.Timesheet;
import org.timesheet.service.GenericDao;

/**
 * DAO of Timesheet.
 */
public interface TimesheetDao extends GenericDao<Timesheet, Long> {
    // no additional business operations atm
}

Time for implementation. We will just extend HibernateDao and implement coresponding interface. We need those concrete classes, because this will be injected to the corresponding fields (which are declared by interface). Maybe you’ve heared something about this approach – it’s called programming to interfaces and it is something you definitelly want to embrace.

package org.timesheet.service.impl;

import org.hibernate.Query;
import org.springframework.stereotype.Repository;
import org.timesheet.domain.Employee;
import org.timesheet.service.dao.EmployeeDao;

@Repository("employeeDao")
public class EmployeeDaoImpl extends HibernateDao<Employee, Long> implements EmployeeDao {

    @Override
    public boolean removeEmployee(Employee employee) {
        Query employeeTaskQuery = currentSession().createQuery(
                "from Task t where :id in elements(t.assignedEmployees)");
        employeeTaskQuery.setParameter("id", employee.getId());

        // employee mustn't be assigned on no task
        if (!employeeTaskQuery.list().isEmpty()) {
            return false;
        }

        Query employeeTimesheetQuery = currentSession().createQuery(
                "from Timesheet t where t.who.id = :id");
        employeeTimesheetQuery.setParameter("id", employee.getId());

        // employee mustn't be assigned to any timesheet
        if (!employeeTimesheetQuery.list().isEmpty()) {
            return false;
        }

        // ok, remove as usual
        remove(employee);
        return true;

    }
}
package org.timesheet.service.impl;

import org.hibernate.Query;
import org.springframework.stereotype.Repository;
import org.timesheet.domain.Manager;
import org.timesheet.service.dao.ManagerDao;

@Repository("managerDao")
public class ManagerDaoImpl extends HibernateDao<Manager, Long> implements ManagerDao {

    @Override
    public boolean removeManager(Manager manager) {
        Query managerQuery = currentSession().createQuery(
                "from Task t where t.manager.id = :id");
        managerQuery.setParameter("id", manager.getId());

        // manager mustn't be assigned on no task
        if (!managerQuery.list().isEmpty()) {
            return false;
        }

        // ok, remove as usual
        remove(manager);
        return true;
    }
}
package org.timesheet.service.impl;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.springframework.stereotype.Repository;
import org.timesheet.domain.Task;
import org.timesheet.domain.Timesheet;
import org.timesheet.service.dao.TaskDao;

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

@Repository("taskDao")
public class TaskDaoImpl extends HibernateDao<Task, Long> implements TaskDao {

    @Override
    public boolean removeTask(Task task) {
        Query taskQuery = currentSession().createQuery(
                "from Timesheet t where t.task.id = :id");
        taskQuery.setParameter("id", task.getId());

        // task mustn't be assigned to no timesheet
        if (!taskQuery.list().isEmpty()) {
            return false;
        }

        // ok, remove as usual
        remove(task);
        return true;
    }

    @Override
    public List<Task> list() {
        return currentSession().createCriteria(Task.class)
                .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
                .list();
    }
}
package org.timesheet.service.impl;

import org.hibernate.Criteria;
import org.springframework.stereotype.Repository;
import org.timesheet.domain.Timesheet;
import org.timesheet.service.dao.TimesheetDao;

import java.util.List;

@Repository("timesheetDao")
public class TimesheetDaoImpl extends HibernateDao<Timesheet, Long> implements TimesheetDao {

    @Override
    public List<Timesheet> list() {
        return currentSession().createCriteria(Timesheet.class)
                .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
                .list();
    }
}

I want you to note that we have Spring’s @Repository annotation on every DAO class. That’s because we won’t create them by hand, but have them injected and managed by Spring’s IoC container. By the way – this is the newer annotation oriented approach. No XML configuration, Spring will figure it out for us ;) We can use plenty of annotations that register classes as beans:

  • @Component – autoscan component (Spring bean)
  • @Repository – component in persistence layer (usually DAO)
  • @Service – component in service layer
  • @Controller – controller in MVC architecture

Another interseting thing is that we pass string value to @Repository annotation. I’ll quote Spring’s javadoc here, since it’s clearest explanation: “The value may indicate a suggestion for a logical component name, to be turned into a Spring bean in case of an autodetected component.”

Now – time for testing! Create new package: /src/test/java/org/timesheet/service/dao and put tests there.

We will be using some external SQL scripts for verifying database status. Under src/main/resources create folder sql. Now let’s add two scripts; cleanup.sql and create-data.sql. We’ll use now only cleanup.sql script, create-data.sql will be used later.

create-data.sql

-- delete old data
delete from task_employee;
delete from timesheet;
delete from task;
delete from employee;
delete from manager;

-- add few employees
insert into employee values(1, 'management', 'Steve Jobs');
insert into employee values(2, 'management', 'Bill Gates');
insert into employee values(3, 'engineering', 'Steve Wozniak');
insert into employee values(4, 'engineering', 'Paul Allen');

-- add few managers
insert into manager values(1, 'Eric Schmidt');
insert into manager values(2, 'Steve Ballmer');

-- add some tasks
insert into task values(1, 0, 'task 1', 1);
insert into task values(2, 0, 'task 2', 2);

-- connect tasks to some employees
insert into task_employee values (1, 1);
insert into task_employee values (1, 3);
insert into task_employee values (1, 4);
insert into task_employee values (2, 2);
insert into task_employee values (2, 1);

-- create some timesheets on tasks
insert into timesheet values(1,
	5, -- hours
	1, -- first task
	1 -- employee steve jobs
);

insert into timesheet values(2,
	8, -- hours
	2, -- second task
	3 -- employee bill gates
);

cleanup.sql

delete from task_employee;
delete from timesheet;
delete from task;
delete from employee;
delete from manager;

You don’t have to use my data; feel free to create some on your own. Just make sure they make somehow sense to you.

Before we write test we need new Spring bean. It’s called jdbcTemplate and it’s well known facility for working with JDBC in Spring. It’s basically wrapper upon plain JDBC that simplifies lot of things. Thanks to this we can run script with simple call as you’ll see later.

For now, add this bean to your persistence-beans.xml Spring Config file:

    <bean id="jdbcTemplate"
            class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
        <constructor-arg type="javax.sql.DataSource" ref="dataSource"/>
    </bean>

I won’t be paying any special attention to every test, so let’s only briefly talk about what to test and how. We’re testing our DAOs and we need to make sure that basic CRUD operations work properly. We’re cleaning all the data after each test method and if necessary, we’re creating them before the test method runs. Basic idea of our tests is like so:

  • If something was added, check if can find that
  • If something was removed, check that we can’t find that anymore
  • Add couple of items to database, count them and verify they’ve been added
  • Update item, save it. Find it and check that it has been changed

I like to think of those tests like integration tests more like unit tests. In huge domain similar tests would require (unlike plain unit tests) quite large amount of time to run. This time we will create special base class called org.timesheet.DomainAwareBase. This extends AbstractJUnit4SpringContextTests so we can have our DAOs autowired, but it also deletes all data from database before any test method is executed using deleteScript.

package org.timesheet;

import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.jdbc.SimpleJdbcTestUtils;

/**
 * Base makes sure that before any test empty database is available.
 */
@ContextConfiguration(locations = {"/persistence-beans.xml"})
public abstract class DomainAwareBase extends AbstractJUnit4SpringContextTests {

    private final String deleteScript = "src/main/resources/sql/cleanup.sql";

    @Autowired
    private SimpleJdbcTemplate jdbcTemplate;

    @Before
    public void deleteAllDomainEntities() {
        SimpleJdbcTestUtils.executeSqlScript(jdbcTemplate,
                new FileSystemResource(deleteScript), false);
    }
}

About autowiring and tooling:
For me, tooling is specially important when autowiring beans. It’s little hard to navigate through code withou any additional support. For example if you’re using ultimate edition of IntelliJ IDEA you can navigate directly from field to autowired dependency because IntelliJ will add little marker.

Or you can also see autowired depencies together with those declared in XML in dependencies view.

Let’s see the code for tests now:

package org.timesheet.service.dao;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.timesheet.DomainAwareBase;
import org.timesheet.domain.Employee;
import org.timesheet.domain.Manager;
import org.timesheet.domain.Task;
import org.timesheet.domain.Timesheet;

import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.*;

@ContextConfiguration(locations = "/persistence-beans.xml")
public class EmployeeDaoTest extends DomainAwareBase {

    @Autowired
    private EmployeeDao employeeDao;

    @Autowired
    private ManagerDao managerDao;

    @Autowired
    private TaskDao taskDao;

    @Autowired
    private TimesheetDao timesheetDao;

    @Test
    public void testAdd() {
        int size = employeeDao.list().size();
        employeeDao.add(new Employee("test-employee", "hackzorz"));

        // list should have one more employee now
        assertTrue (size < employeeDao.list().size());
    }
    
    @Test
    public void testUpdate() {
        Employee employee = new Employee("test-employee", "hackzorz");
        employeeDao.add(employee);
        employee.setName("updated");

        employeeDao.update(employee);
        Employee found = employeeDao.find(employee.getId());
        assertEquals("updated", found.getName());
    }

    @Test
    public void testFind() {
        Employee employee = new Employee("test-employee", "hackzorz");
        employeeDao.add(employee);

        Employee found = employeeDao.find(employee.getId());
        assertEquals(found, employee);
    }

    @Test
    public void testList() {
        assertEquals(0, employeeDao.list().size());
        
        List<Employee> employees = Arrays.asList(
                new Employee("test-1", "testers"),
                new Employee("test-2", "testers"),
                new Employee("test-3", "testers"));
        for (Employee employee : employees) {
            employeeDao.add(employee);
        }

        List<Employee> found = employeeDao.list();
        assertEquals(3, found.size());
        for (Employee employee : found) {
            assertTrue(employees.contains(employee));
        }
    }

    @Test
    public void testRemove() {
        Employee employee = new Employee("test-employee", "hackzorz");
        employeeDao.add(employee);

        // successfully added
        assertEquals(employee, employeeDao.find(employee.getId()));

        // try to remove
        employeeDao.remove(employee);
        assertNull(employeeDao.find(employee.getId()));
    }

    @Test
    public void testRemoveEmployee() {
        Manager manager = new Manager("task-manager");
        managerDao.add(manager);

        Employee employee = new Employee("Jaromir", "Hockey");
        employeeDao.add(employee);

        Task task = new Task("test-task", manager, employee);
        taskDao.add(task);

        Timesheet timesheet = new Timesheet(employee, task, 100);
        timesheetDao.add(timesheet);

        // try to remove -> shouldn't work
        assertFalse(employeeDao.removeEmployee(employee));

        // remove stuff
        timesheetDao.remove(timesheet);
        taskDao.remove(task);

        // should work -> employee is now free
        assertTrue(employeeDao.removeEmployee(employee));
    }

}
package org.timesheet.service.dao;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.timesheet.DomainAwareBase;
import org.timesheet.domain.Employee;
import org.timesheet.domain.Manager;
import org.timesheet.domain.Task;

import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.*;

@ContextConfiguration(locations = "/persistence-beans.xml")
public class ManagerDaoTest extends DomainAwareBase {
    
    @Autowired
    private ManagerDao managerDao;

    @Autowired
    private EmployeeDao employeeDao;

    @Autowired
    private TaskDao taskDao;
    
    @Test
    public void testAdd() {
        int size = managerDao.list().size();
        managerDao.add(new Manager("test-manager"));

        assertTrue (size < managerDao.list().size());
    }

    @Test
    public void testUpdate() {
        Manager manager = new Manager("test-manager");
        managerDao.add(manager);
        manager.setName("updated");

         managerDao.update(manager);
        Manager found = managerDao.find(manager.getId());
        assertEquals("updated", found.getName());
    }

    @Test
    public void testFind() {
        Manager manager = new Manager("test-manager");
        managerDao.add(manager);

        Manager found = managerDao.find(manager.getId());
        assertEquals(found, manager);
    }
    
    @Test
    public void testList() {
        assertEquals(0, managerDao.list().size());
        
        List<Manager> managers = Arrays.asList(
                new Manager("test-1"),
                new Manager("test-2"),
                new Manager("test-3")
        );
        for (Manager manager : managers) {
            managerDao.add(manager);
        }

        List<Manager> found = managerDao.list();
        assertEquals(3, found.size());
        for (Manager manager : found) {
            assertTrue(managers.contains(manager));
        }
    }
    
    @Test
    public void testRemove() {
        Manager manager = new Manager("test-manager");
        managerDao.add(manager);
        
        // successfully added
        assertEquals(manager, managerDao.find(manager.getId()));
        
        // try to remove
        managerDao.remove(manager);
        assertNull(managerDao.find(manager.getId()));
    }

    @Test
    public void testRemoveManager() {
        Manager manager = new Manager("task-manager");
        managerDao.add(manager);

        Employee employee = new Employee("Jaromir", "Hockey");
        employeeDao.add(employee);

        Task task = new Task("test-task", manager, employee);
        taskDao.add(task);

        // try to remove -> shouldn't work
        assertFalse(managerDao.removeManager(manager));

        // remove task
        taskDao.remove(task);

        // should work -> no more tasks for manager
        assertTrue(managerDao.removeManager(manager));
    }
}
package org.timesheet.service.dao;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.timesheet.DomainAwareBase;
import org.timesheet.domain.Employee;
import org.timesheet.domain.Manager;
import org.timesheet.domain.Task;

import java.util.Arrays;
import java.util.List;

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

@ContextConfiguration(locations = "/persistence-beans.xml")
public class TaskDaoTest extends DomainAwareBase {
    
    @Autowired
    private TaskDao taskDao;

    @Autowired
    private ManagerDao managerDao;

    @Autowired
    private EmployeeDao employeeDao;

    @Test
    public void testAdd() {
        int size = taskDao.list().size();

        Task task = newSpringTask();
        taskDao.add(task);

        assertTrue(size < taskDao.list().size());
    }

    @Test
    public void testUpdate() {
        Task task = newSpringTask();
        taskDao.add(task);
        
        // update task
        task.setDescription("Learn Spring 3.1");
        taskDao.update(task);

        Task found = taskDao.find(task.getId());
        assertEquals("Learn Spring 3.1", found.getDescription());
    }

    @Test
    public void testFind() {
        Task task = newSpringTask();
        taskDao.add(task);
        
        assertEquals(task, taskDao.find(task.getId()));
    }
    
    @Test
    public void testList() {
        assertEquals(0, taskDao.list().size());
        Task templateTask = newSpringTask();
        
        List<Task> tasks = Arrays.asList(
                newTaskFromTemplate(templateTask, "1"),
                newTaskFromTemplate(templateTask, "2"),
                newTaskFromTemplate(templateTask, "3")
        );
        for (Task task : tasks) {
            taskDao.add(task);
        }

        List<Task> found = taskDao.list();
        assertEquals(3, found.size());
        for (Task task : found) {
            assertTrue(tasks.contains(task));
        }
    }
    
    @Test
    public void testRemove() {
        Task task = newSpringTask();
        taskDao.add(task);
        
        // successfully added
        assertEquals(task, taskDao.find(task.getId()));
        
        // try to remove
        taskDao.remove(task);
        assertNull(taskDao.find(task.getId()));
    }

    /**
     * @return Dummy task for testing
     */
    private Task newSpringTask() {
        Manager bob = new Manager("Bob");
        managerDao.add(bob);

        Employee steve = new Employee("Steve", "Business");
        Employee woz = new Employee("Woz", "Engineering");
        employeeDao.add(steve);
        employeeDao.add(woz);

        return new Task("Learn Spring", bob, steve, woz);
    }

    /**
     * Creates dummy task fo testing as copy of existing task and
     * adds aditional information to every field.
     * @param templateTask Task to copy
     * @param randomInfo Info to append everywhere
     * @return Random task for testing
     */
    private Task newTaskFromTemplate(Task templateTask, 
            String randomInfo) {
        String description = templateTask.getDescription() 
                + randomInfo;
        
        Manager manager = new Manager(
                templateTask.getManager().getName());
        managerDao.add(manager);

        List<Employee> templateEmployees = templateTask.getAssignedEmployees();
        Employee[] employees = new Employee[templateEmployees.size()];

        int idx = 0;
        for (Employee templateEmployee : templateEmployees) {
            Employee employee = new Employee(
                    templateEmployee.getName() + randomInfo,
                    templateEmployee.getDepartment() + randomInfo);
            employees[idx++] = employee;
            employeeDao.add(employee);
        }

        return new Task(description, manager, employees);
    }
}
package org.timesheet.service.dao;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.timesheet.DomainAwareBase;
import org.timesheet.domain.Employee;
import org.timesheet.domain.Manager;
import org.timesheet.domain.Task;
import org.timesheet.domain.Timesheet;

import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.*;

@ContextConfiguration(locations = "/persistence-beans.xml")
public class TimesheetDaoTest extends DomainAwareBase {

    @Autowired
    private TimesheetDao timesheetDao;

    // daos needed for integration test of timesheetDao
    @Autowired
    private TaskDao taskDao;

    @Autowired
    private EmployeeDao employeeDao;

    @Autowired
    private ManagerDao managerDao;

    // common fields for timesheet creation
    private Task task;
    private Employee employee;

    @Override
    public void deleteAllDomainEntities() {
        super.deleteAllDomainEntities();
        setUp();
    }

    public void setUp() {
        employee = new Employee("Steve", "Engineering");
        employeeDao.add(employee);

        Manager manager = new Manager("Bob");
        managerDao.add(manager);

        task = new Task("Learn Spring", manager, employee);
        taskDao.add(task);
    }

    @Test
    public void testAdd() {
        int size = timesheetDao.list().size();

        Timesheet timesheet = newTimesheet();
        timesheetDao.add(timesheet);

        assertTrue (size < timesheetDao.list().size());
    }

    @Test
    public void testUpdate() {
        Timesheet timesheet = newTimesheet();
        timesheetDao.add(timesheet);

        // update timesheet
        timesheet.setHours(6);
        taskDao.update(timesheet.getTask());
        timesheetDao.update(timesheet);

        Timesheet found = timesheetDao.find(timesheet.getId());
        assertTrue(6 == found.getHours());
    }

    @Test
    public void testFind() {
        Timesheet timesheet = newTimesheet();
        timesheetDao.add(timesheet);

        assertEquals(timesheet, timesheetDao.find(timesheet.getId()));
    }

    @Test
    public void testList() {
        assertEquals(0, timesheetDao.list().size());
        Timesheet templateTimesheet = newTimesheet();
        
        List<Timesheet> timesheets = Arrays.asList(
                newTimesheetFromTemplate(templateTimesheet, 4),
                newTimesheetFromTemplate(templateTimesheet, 7),
                newTimesheetFromTemplate(templateTimesheet, 10)
        );
        for (Timesheet timesheet : timesheets) {
            timesheetDao.add(timesheet);
        }

        List<Timesheet> found = timesheetDao.list();
        assertEquals(3, found.size());
        for (Timesheet timesheet : found) {
            assertTrue (timesheets.contains(timesheet));
        }
    }

    @Test
    public void testRemove() {
        Timesheet timesheet = newTimesheet();
        timesheetDao.add(timesheet);
        
        // successfully added
        assertEquals(timesheet, timesheetDao.find(timesheet.getId()));
        
        // try to remoce
        timesheetDao.remove(timesheet);
        assertNull (timesheetDao.find(timesheet.getId()));
    }

    /**
     * @return  Dummy timesheet for testing
     */
    private Timesheet newTimesheet() {
        return new Timesheet(employee, task, 5);
    }

    private Timesheet newTimesheetFromTemplate(Timesheet template,
            Integer hours) {
        return new Timesheet(
                template.getWho(),
                template.getTask(),
                hours
        );
    }
}

You can run your tests as individual classes or alltogether from your IDE, or you can run them as “test” goal from Maven like so (switch to project directory):

$ mvn test

Tests are pretty much similar so if you can understand at least one of them, you’re probably fine just to copy-paste them to your own project. If you care to spend little more time, feel free to write them on your own and do little experimentation to get Hibernate know a little better.

As for DAOs, we’re pretty much done. One thing left though – our TimesheetService interface. That’s the set of business operations that we’re intersted in, so let’s implement it using Hibernate. We’ll put TimesheetServiceImpl class under org.timesheet.service.impl package:

package org.timesheet.service.impl;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.timesheet.domain.Employee;
import org.timesheet.domain.Manager;
import org.timesheet.domain.Task;
import org.timesheet.service.TimesheetService;
import org.timesheet.service.dao.TaskDao;

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

@Transactional(propagation= Propagation.REQUIRED, readOnly=false)
@Service("timesheetService")
public class TimesheetServiceImpl implements TimesheetService {

    // dependencies
    private SessionFactory sessionFactory;
    private TaskDao taskDao;

    private Random random = new Random();

    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @Autowired
    public void setTaskDao(TaskDao taskDao) {
        this.taskDao = taskDao;
    }

    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public TaskDao getTaskDao() {
        return taskDao;
    }

    private Session currentSession() {
        return sessionFactory.getCurrentSession();
    }

    @Override
    public Task busiestTask() {
        List<Task> tasks = taskDao.list();
        if (tasks.isEmpty()) {
            return null;
        }
        
        Task busiest = tasks.get(0);
        for (Task task : tasks) {
            if (task.getAssignedEmployees().size() > busiest.getAssignedEmployees().size()) {
                busiest = task;
            }
        }
        
        return busiest;
    }

    @Override
    public List<Task> tasksForEmployee(Employee employee) {
        List<Task> allTasks = taskDao.list();
        List<Task> tasksForEmployee = new ArrayList<Task>();
        
        for (Task task : allTasks) {
            if (task.getAssignedEmployees().contains(employee)) {
                tasksForEmployee.add(task);
            }
        }

        return tasksForEmployee;
    }

    @Override
    public List<Task> tasksForManager(Manager manager) {
        Query query = currentSession()
                .createQuery("from Task t where t.manager.id = :id");
        query.setParameter("id", manager.getId());
        return query.list();
    }
}

Note that we use @Service annotation this time (we’ve talked about these before). Also we’re injecting some DAOs with setter injection. Some business method aren’t implemented most effectively, but we’re demonstrating that we can either mix generic DAO logic or create our own queries using HQL. We could have picked Criteria API, it doesn’t really matter now. The biggest downside about HQL is that it’s plain strings so it’s not refactoring friendly – unless you use proper tooling. For example, IntelliJ has autocompletion even for plain strings. It just figures out that you’re writing HQL. Pretty useful is also HQL console, IntelliJ has one and there’s plugin for Eclipse.

IntelliJ highlighting & autocompletion for HQL:

Now we should test this service. This time we don’t want to create instances of entities in Java, we’ll use external SQL scripts we created before – for setting up and cleaning the data.

Let’s put test class TimesheetServiceTest in package org.timesheet.service in src/test/java folder. In the following code, note how we’re using jdbcTemplate bean:

package org.timesheet.service;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.jdbc.SimpleJdbcTestUtils;
import org.timesheet.domain.Employee;
import org.timesheet.domain.Manager;
import org.timesheet.domain.Task;
import org.timesheet.service.dao.EmployeeDao;
import org.timesheet.service.dao.ManagerDao;

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

@ContextConfiguration(locations = "/persistence-beans.xml")
public class TimesheetServiceTest extends AbstractJUnit4SpringContextTests {
    
    @Autowired
    private TimesheetService timesheetService;

    // resources for accessing data during the testing
    @Autowired
    private SimpleJdbcTemplate jdbcTemplate;
    @Autowired
    private EmployeeDao employeeDao;
    @Autowired
    private ManagerDao managerDao;

    private final String createScript = "src/main/resources/sql/create-data.sql";
    private final String deleteScript = "src/main/resources/sql/cleanup.sql";

    @Before
    public void insertData() {
        SimpleJdbcTestUtils.executeSqlScript(jdbcTemplate,
                new FileSystemResource(createScript), false);
    }

    @After
    public void cleanUp() {
        SimpleJdbcTestUtils.executeSqlScript(jdbcTemplate,
                new FileSystemResource(deleteScript), false);
    }

    @Test
    public void testBusiestTask() {
        Task task = timesheetService.busiestTask();
        assertTrue(1 == task.getId());
    }
    
    @Test
    public void testTasksForEmployees() {
        Employee steve = employeeDao.find(1L);
        Employee bill = employeeDao.find(2L);
        
        assertEquals(2, timesheetService.tasksForEmployee(steve).size());
        assertEquals(1, timesheetService.tasksForEmployee(bill).size());
    }
    
    @Test
    public void testTasksForManagers() {
        Manager eric = managerDao.find(1L);
        assertEquals(1, timesheetService.tasksForManager(eric).size());
    }
    
}

Allright, that’s it. We’ve implemented DAO and service layer. This included quite lot of code, so before you continue make sure that you’re project structure looks like this:

Finally it’s time to write some controllers and that’s what we’ll do that in the next part of this tutorial.

Back to main tutorial page

Part 2 – Persistence layer – writing entities and configuring Hibernate

Welcome to the second part of this tutorial. Don’t freak out when you see how long this article is – I promise you it’s mostly easy POJOs and some generated code ;)

Before we start, we need to update our Maven dependencies, because we will be using Hibernate and Spring now. Add following dependencies to your pom.xml:

		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>3.6.8.Final</version>
		</dependency>
                <dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.6</version>
		</dependency>
		
		<!-- spring framework -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>3.1.0.RELEASE</version>
		</dependency>
                <dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>3.1.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>3.1.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>3.1.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>3.1.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>3.1.0.RELEASE</version>
		</dependency>

If you’re new to Maven you might be wondering now – how do you know these? Where can I get them? Well, just go to http://mvnrepository.com/ and type what your ale looking for. You will get complete code for maven dependencies. If you have ever tried to assemble Spring or Hibernate application yourself without using Maven, you probably know how painful it was. With Maven things are so much easier.

Also note, that we have included dependency to MySQL’s connector. If you’ve decided to use other database, don’t forget to change this.

With Hibernate we have 2 options how to turn our POJOs to entites. Either we use XML and create mapping files, or we will put some meta information to our code (java annotations). Some people are afraid of those and consider this as coupling with framework. It is true that you will need javax.persistence annotations at your classpath, but we won’t be implementing interfaces or extending framework classes. We will just add some meta information to our code and POJOs will still be simply POJOs with some extra information.

We will turn our POJOs to entities now. We will need to do following changes:

  • Add default no-args constructor for Hibernate
  • Create getters and setters for fields
  • add equals and hashCode methods.
  • Add persistence annotations. Note that we also use @Table annotation to distinguish between Java and SQL naming conventions.
  • Add id fields. These will be primary keys in our relational database.

This is quite lot of boilerplate code, so let your IDE help you. Most modern IDEs will generate construcors, getters, setters, equals and hashCode for you.

package org.timesheet.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "employee")
public class Employee {

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	private String name;
	private String department;
	
	public Employee() {
	}

	public Employee(String name, String department) {
		this.name = name;
		this.department = department;
	}
	
	public String getName() {
		return name;
	}
	
	public String getDepartment() {
		return department;
	}
	
	public Long getId() {
		return id;
	}
	
	public void setId(Long id) {
		this.id = id;
	}

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

	public void setDepartment(String department) {
		this.department = department;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", department="
				+ department + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((department == null) ? 0 : department.hashCode());
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof Employee)) {
			return false;
		}
		Employee other = (Employee) obj;
		if (department == null) {
			if (other.department != null) {
				return false;
			}
		} else if (!department.equals(other.department)) {
			return false;
		}
		if (id == null) {
			if (other.id != null) {
				return false;
			}
		} else if (!id.equals(other.id)) {
			return false;
		}
		if (name == null) {
			if (other.name != null) {
				return false;
			}
		} else if (!name.equals(other.name)) {
			return false;
		}
		return true;
	}
	
}
package org.timesheet.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "manager")
public class Manager {
	
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	private String name;
	
	public Manager() {
	}

	public Manager(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}

	public Long getId() {
		return id;
	}

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

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

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof Manager)) {
			return false;
		}
		Manager other = (Manager) obj;
		if (id == null) {
			if (other.id != null) {
				return false;
			}
		} else if (!id.equals(other.id)) {
			return false;
		}
		if (name == null) {
			if (other.name != null) {
				return false;
			}
		} else if (!name.equals(other.name)) {
			return false;
		}
		return true;
	}

	@Override
	public String toString() {
		return "Manager [id=" + id + ", name=" + name + "]";
	}
	
}
package org.timesheet.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name="timesheet")
public class Timesheet {

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	
	@OneToOne
	@JoinColumn(name = "employee_id")
	private Employee who;
	
	@OneToOne
	@JoinColumn(name = "task_id")
	private Task task;
	private Integer hours;
	
	public Timesheet() {
	}
	
	public Timesheet(Employee who, Task task, Integer hours) {
		this.who = who;
		this.task = task;
		this.hours = hours;
	}

	public Employee getWho() {
		return who;
	}

	public Task getTask() {
		return task;
	}
	
	public Integer getHours() {
		return hours;
	}
	
	public Long getId() {
		return id;
	}

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

	public void setWho(Employee who) {
		this.who = who;
	}

	public void setTask(Task task) {
		this.task = task;
	}

	public void setHours(Integer hours) {
		this.hours = hours;
	}

	/**
	 * Manager can alter hours before closing task
	 * @param hours New amount of hours
	 */
	public void alterHours(Integer hours) {
		this.hours = hours;
	}

	@Override
	public String toString() {
		return "Timesheet [id=" + id + ", who=" + who + ", task=" + task
				+ ", hours=" + hours + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((hours == null) ? 0 : hours.hashCode());
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((task == null) ? 0 : task.hashCode());
		result = prime * result + ((who == null) ? 0 : who.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof Timesheet)) {
			return false;
		}
		Timesheet other = (Timesheet) obj;
		if (hours == null) {
			if (other.hours != null) {
				return false;
			}
		} else if (!hours.equals(other.hours)) {
			return false;
		}
		if (id == null) {
			if (other.id != null) {
				return false;
			}
		} else if (!id.equals(other.id)) {
			return false;
		}
		if (task == null) {
			if (other.task != null) {
				return false;
			}
		} else if (!task.equals(other.task)) {
			return false;
		}
		if (who == null) {
			if (other.who != null) {
				return false;
			}
		} else if (!who.equals(other.who)) {
			return false;
		}
		return true;
	}
}

And finally, here’s Task entity when we needed to use also @ManyToMany mapping. That’s because one employee can work on multiple tasks and one task can have assigned multiple employees. We’ve defined how our m:n will look like, using @JoinTable and @JoinColumn annotations.

package org.timesheet.domain;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Entity
@Table(name = "task")
public class Task {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "task_employee",
            joinColumns = {@JoinColumn(name = "task_id")},
            inverseJoinColumns = {@JoinColumn(name = "employee_id")}
    )
    private List<Employee> assignedEmployees = new ArrayList<Employee>();

    @OneToOne
    @JoinColumn(name = "manager_id")
    private Manager manager;

    private String description;
    boolean completed;

    public Task() {
    }

    public Task(String description, Manager manager, Employee... employees) {
        this.description = description;
        this.manager = manager;
        assignedEmployees.addAll(Arrays.asList(employees));
        completed = false;
    }

    public Manager getManager() {
        return manager;
    }

    public List<Employee> getAssignedEmployees() {
        return assignedEmployees;
    }

    public void addEmployee(Employee e) {
        assignedEmployees.add(e);
    }

    public void removeEmployee(Employee e) {
        assignedEmployees.remove(e);
    }

    public Long getId() {
        return id;
    }

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

    public boolean isCompleted() {
        return completed;
    }

    public void setCompleted(boolean completed) {
        this.completed = completed;
    }

    public void setAssignedEmployees(List<Employee> assignedEmployees) {
        this.assignedEmployees = assignedEmployees;
    }

    public void setManager(Manager manager) {
        this.manager = manager;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        Task task = (Task) o;

        if (completed != task.completed) {
            return false;
        }
        if (description != null ? !description.equals(task.description) : task.description != null) {
            return false;
        }
        if (id != null ? !id.equals(task.id) : task.id != null) {
            return false;
        }
        if (manager != null ? !manager.equals(task.manager) : task.manager != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = id != null ? id.hashCode() : 0;
        result = 31 * result + (manager != null ? manager.hashCode() : 0);
        result = 31 * result + (description != null ? description.hashCode() : 0);
        result = 31 * result + (completed ? 1 : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Task{" +
                "id=" + id +
                ", assignedEmployees=" + assignedEmployees +
                ", manager=" + manager +
                ", description='" + description + '\'' +
                ", completed=" + completed +
                '}';
    }
}
 

So we didn’t really do anything special to model. If you fancy some UML look at the following picture, relations are same as before.

Okay we have entites, now let’s create database. Pick some tool for database management (even plain terminal is fine) and create timesheet database like so (by default mysql will install to /usr/local/mysql/bin/mysql at Mac OS X):

$ mysql -u root
mysql > create database timesheet;

If you’ve ever configured Hibernate before you probably know, that you need quite many files and boilerplate code when dealing for example with SessionFactory. These things are much simpler with Spring.

We will now create our first Spring Bean Configuration File – it’s file where we register beans for Spring container. If I had to explain what’s this file to someone who doesn’t know what Spring is at all – it’s kind of magic bag where Spring container can find objects.

Modern IDEs will help you get all the XML namespaces right, for example you can see pictures from STS wizard. NetBeans has something similar and IntelliJ resolves namespaces on the fly.
Name configuration file persistence-beans.xml and we will put it under src/main/resources folder.

So setting up Hibernate, transactions, annotation config and so on is as simple as creating few beans in XML file. Alternatively, we can use Java Config for Spring, but XML configs are still used much more, so we’ll stick to those. I don’t want to discourage you from using Java Config though! XML config is much much more popular at this moment, but I can’t guarantee that for the next few years.
I’ve commented every bean to make sure you understand what we did there before proceeding. If you want to get some visual grasp of connections between beans you can again use some tooling – in STS it’s called Bean Graph, in IntelliJ it’s Dependencies. You can see sample of dependencies on the picture below.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 

http://www.springframework.org/schema/beans/spring-beans.xsd


http://www.springframework.org/schema/context


http://www.springframework.org/schema/context/spring-context-3.1.xsd


http://www.springframework.org/schema/tx


http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

	<!-- we can use annotations -->
	<context:annotation-config />  
	
	<!-- package to look for annotated classes -->
	<context:component-scan base-package="org.timesheet.service.impl" />
	
	<!-- we will manage transactions with annotations -->
	<tx:annotation-driven />

	<!-- data source for our database -->
	<bean id="dataSource" 
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" 
			value="com.mysql.jdbc.jdbc2.optional.MysqlDataSource" />
		<property name="url" value="jdbc:mysql://localhost/timesheet" />
		<property name="username" value="root" />
		<property name="password" value="" />
	</bean>
	
	<!-- configure hibernate session factory -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="annotatedClasses" >
			<list>
				<value>org.timesheet.domain.Employee</value>
				<value>org.timesheet.domain.Manager</value>
				<value>org.timesheet.domain.Task</value>
				<value>org.timesheet.domain.Timesheet</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.hbm2ddl.auto">update</prop>
			</props>
		</property>	
	</bean>
	
</beans>

Okay, that was quite lot of configuration, he? What’s not so good is that we’ve placed names of our entities to XML as plain text, so it isn’t refactoring friendly. But I think for this tutorial it’s acceptable :) Let’s write integration test for Hibernate so we know that everything is set up properly.

package org.timesheet.integration;

import static org.junit.Assert.*;

import org.hibernate.SessionFactory;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;

@ContextConfiguration(locations = "/persistence-beans.xml")
public class HibernateConfigurationTest extends AbstractJUnit4SpringContextTests {
	
	@Autowired
	private SessionFactory sessionFactory;

	@Test
	public void testHibernateConfiguration() {
		// Spring IOC container instantiated and prepared sessionFactory
		assertNotNull (sessionFactory); 
	}

}

I want you to note 2 things here. First, we extend AbstractJUnit4SpringContextTests class. We tell it where it should look for actual XML config with spring beans. Otherwise we would have to create Spring container by ourselves, which means more boilerplate code.

Second, we use @Autowired annotation. That means we don’t create instance of SessionFactory by hand using new operator, but we will have it Autowired (Injected) by Spring container! That’s one of the most important purposes of Spring container – depend on interfaces and have implementations injected instead of creating them by hand.
Everything should work now and I think that’s enough for this part.

If you like you can check plain SQL, and see tables are here, do it like so:

mysql> use timesheet;
mysql> show tables;
+---------------------+
| Tables_in_timesheet |
+---------------------+
| employee            |
| manager             |
| task                |
| task_employee       |
| timesheet           |
+---------------------+
5 rows in set (0.00 sec)

In the next part of this tutorial, we will implement DAOs and services and write unit tests for them.

Back to main tutorial page

Part 1 – Designing the domain model and the service layer

We are going to build application for timesheet management. So let’s think about some uses cases and entities first. Let me write them in the few bullets:

  1. Task is assigned to employee by manager. One task can be assigned to many employees.
  2. Employee fills the amount of hours that he worked on certain task to the system.
  3. Manager/Employee views reports on timesheets (timesheets can be altered).

Let’s revisit those points a little and let’s try to transform this plain human language to some relations and entities that programmer can spot.

  • Entities: Manager, Employee, Timesheet, Task

Okay, we should now have better grasp about the domain, so let’s create maven project and implement classes. With Maven you get nice and clean project structure. All you need is installed Maven and having pom.xml in your project. You can either do that “by hand” and building application via terminal (in this case just create regular project and add pom.xml file). I prefer using some additional tooling. IntelliJ IDEA, NetBeans and Springsource Tool Suite have out of the box Maven support. If you’re using plain Eclipse, check m2eclipse plugin.

In either way, here is some basic Maven configuration for our project:


<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>org.timesheet</groupId>
	<artifactId>org.timesheet</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>Timesheet Management On Spring</name>
  
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

Now let’s implement domain model. Create package org.timesheet.domain and define following classes.

package org.timesheet.domain;

public class Employee {

	private String name;
	private String department;

	public Employee(String name, String department) {
		this.name = name;
		this.department = department;
	}
	
	public String getName() {
		return name;
	}
	
	public String getDepartment() {
		return department;
	}
}
package org.timesheet.domain;

public class Manager {

	private String name;

	public Manager(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
}
package org.timesheet.domain;

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

public class Task {

	private List<Employee> assignedEmployees = new ArrayList<Employee>();
	private Manager manager;
	private boolean completed;
	private String description;
	
	public Task(String description, Manager manager, Employee... employees) {
        	this.description = description;
        	this.manager = manager;
		assignedEmployees.addAll(Arrays.asList(employees));
		completed = false;
	}

	public Manager getManager() {
		return manager;
	}
	
	public List<Employee> getAssignedEmployees() {
		return assignedEmployees;
	}
	
	public void addEmployee(Employee e) {
		assignedEmployees.add(e);
	}
	
	public void removeEmployee(Employee e) {
		assignedEmployees.remove(e);
	}
	
	public void completeTask() {
		completed = true;
	}
}
package org.timesheet.domain;

public class Timesheet {

	private Employee who;
	private Task task;
	private Integer hours;
	
	public Timesheet(Employee who, Task task, Integer hours) {
		this.who = who;
		this.task = task;
		this.hours = hours;
	}

	public Employee getWho() {
		return who;
	}

	public Task getTask() {
		return task;
	}
	
	public Integer getHours() {
		return hours;
	}
	
	/**
	 * Manager can alter hours before closing task
	 * @param hours New amount of hours
	 */
	public void alterHours(Integer hours) {
		this.hours = hours;
	}

	@Override
	public String toString() {
		return "Timesheet [who=" + who + ", task=" + task + ", hours=" + hours
				+ "]";
	}

}

As you can see, Manager and Employee classes don’t have many properties, they’re here just for the sake of having type safe model. In the “real world”, they’d probably have various other properties like surname, birthday, address and so on, maybe even common parent class.
Also, we don’t really care about various constraints now. For example, we can only fill integer hours on tasks and so on.

Now it’s time to define our service layer – define business operations and establish interface for those. So let’s make package org.timesheet.service. At first, we will create GenericDao interface, where we will define basic CRUD operations for every entity in the system.

package org.timesheet.service;

import java.util.List;

public interface GenericDao<E, K> {

	void add(E entity);
	
	void update(E entity);
	
	void remove(E entity);
	
	E find(K key);
	
	List<E> list();
	
}

For now, let’s not worry about the actual persistence layer – let’s create some dummy implementation and store all the data in memory. We will put it in to the new package – org.timesheet.service.impl. Don’t worry, later we’ll use Hibernate for this. Here is the code for the dummy implementation:

package org.timesheet.service.impl;

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

import org.timesheet.service.GenericDao;

public class InMemoryDao<E, K> implements GenericDao<E, K> {
	
	private List<E> entities = new ArrayList<E>();

	@Override
	public void add(E entity) {
		entities.add(entity);
	}

	@Override
	public void update(E entity) {
		throw new UnsupportedOperationException("Not supported in dummy in-memory impl!");
	}

	@Override
	public void remove(E entity) {
		entities.remove(entity);
	}

	@Override
	public E find(K key) {
		if (entities.isEmpty()) {
			return null;
		}
		// just return the first one sice we are not using any keys ATM
		return entities.get(0);
	}

	@Override
	public List<E> list() {
		return entities;
	}

}

Next, we will write our first simple test. We will now add our first dependency into pom.xml file to JUnit library. Because it’s the first one, we also need to wrap it into dependencies element like so:

	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.10</version>
		</dependency>
	</dependencies>

Here’s our first very simple unit test for Employee DAO. We won’t do others now, since we don’t really have anything to test yet. What’s more important though, is how we depend on implementation of DAO in the test (we use new InMemoryDao…). This is bad, because we should only test public API of defined interface. Later in this tutorial, you will see how Spring helps us to solve such problems.

package org.timesheet.service;

import static org.junit.Assert.*;

import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.timesheet.domain.Employee;
import org.timesheet.service.impl.InMemoryDao;

public class EmployeeDaoTest {
	
	private GenericDao<Employee, Long> employeeDao = new InMemoryDao<Employee, Long>();
	
	@Before
	public void setUp() {
		for (int i = 0; i < 5; i++) {
			Employee e = new Employee("Mike " + i, "IT");
			employeeDao.add(e);
		}
	}
	
	@Test
	public void testAdd() {
		int oldSize = employeeDao.list().size();
		Employee e = new Employee("Bob", "IT");
		employeeDao.add(e);
		int newSize = employeeDao.list().size();
		
		assertFalse (oldSize == newSize);
	}
	
	@Test
	public void testRemove() {
		int oldSize = employeeDao.list().size();
		Employee e = employeeDao.find(1L);
		employeeDao.remove(e);
		int newSize = employeeDao.list().size();
		
		assertFalse (oldSize == newSize);
	}
	
	@Test
	public void testUpdate() {
		//TODO: need real implementation
	}
	
	@Test
	public void testList() {
		List<Employee> list = employeeDao.list();
		assertNotNull (list);
		assertFalse (list.isEmpty());
	}

}

If you want, you can also write unit tests for remaining tests for the other DAOs. But since we don’t have proper implementation to test now, we’ll do it later together.

Things are not always so easy though. It’s not only about CRUD operations, it’s also about business operations that are not generic enough to be expressed in simple DAOs. So let’s define few business operations and create separate service for them. We’ll call this service TimesheetService.

package org.timesheet.service;

import org.timesheet.domain.Employee;
import org.timesheet.domain.Manager;
import org.timesheet.domain.Task;

import java.util.List;

/**
 * Business that defines operations on timesheets
 */
public interface TimesheetService {
	
	/**
	 * @return Finds the busiest task (with the most of employees).
     * Returns {@code null} when tasks are empty.
	 */
	Task busiestTask();
	
	/**
	 * Finds all the tasks for the employee.
	 * @param e Employee
	 * @return Tasks
	 */
	List<Task> tasksForEmployee(Employee e);
	
	/**
	 * Finds all the tasks for the manager.
	 * @param m Manager
	 * @return Tasks
	 */
	List<Task> tasksForManager(Manager m);
	
}

Okay, so far so good. You have now idea what’s the business domain we will be using in the next examples. You might be wondering now – we haven’t use any Spring yet, why? Remember that Spring’s original purpose is to simplify enterprise java development and encourage POJO development model. So it will be very easy to use Spring with this basic model, so we won’t have our core logic mixed with unnecessary dependencies.

On the picture below there is structure of the project we’ve built so far, so make sure you’re good.
New Maven Project 01

In the next part, we will implement our business layer, persistence layer and write some unit tests.

Back to main tutorial page

Shall we do some Spring together?

About: In this tutorial I will show you, how to write Spring webapplication from the scratch and we will do it with step-by-step approach. Maybe you’ve already tried some Spring but you didn’t have the opportunity or will to use more than one particular Spring technology. Maybe you gave up because it looked hard to get all the libraries correct. Don’t worry, we will go together through every step and create working application from the scratch.

Targeted audience: Java programmers. If you’re new to Spring, I hope this will help you to start with Spring development. We will specifically look at assembling complete application with handful of technologies.

Prerequisites: You should be familiar with Java programming language. You will require some general knowledge about web applications, Servlet technology, SQL, REST, ORM , unit testing and dependency injection. We won’t go too deep in any of those, but if something is new to you please check at least some wiki pages or presentations or maybe even tutorials.

List of chapters:

Technologies we will use:

  • Spring 3.1 – as Dependency Injection container, as MVC application framework, to simplify lot of boiler plate code …
  • Hibernate – for persistence layer
  • MySQL – as our database. If you’re familiar with Hibernate, feel free to use other database if you want, because we will be generating our schema from the entities.
  • Maven – for managing dependencies on libraries
  • Tomcat – as our web server

Tooling: I don’t want to make you use any specific IDE – feel free to choose any! Just make sure, it has Maven support and that you have installed everything properly. I will be using IntelliJ IDEA, but also SpringSource Tool Suite to make sure I am able to finish tutorial also in other IDE.

What you will NOT find here:
I’ll try to show you most commonly used technologies in today’s enterprise development. That means, how do applications looks today. There are lot of “cooler” projects from SpringSource that have buzz now (like integrating with NoSQL databases or social networks), but we won’t cover those in this tutorial.

Final project:
Using mercurial: hg clone https://bitbucket.org/vrto/spring-tutorial
Or download zip archive.

About the sources:
If you will be following this tutorial step-by-step and copying sources from the listings, don’t forget to use following icon, that will bring up editor with plain text. So the code will look as in my IDE, unlike as in listing component which sometimes makes the code look messy.

So enough of talking, let’s get started!

Part 5 – Adding Spring MVC part 2

In the previous part we’ve implemented controllers for managers and employees. Now that we know our way around, we’ll do little (but just little) more complicated stuff – controllers for tasks & timesheets.

So let’s start with org.timesheet.web.TaskController. First create a class and this time we will be accessing richer domain, so we’ll need to autowire three DAOS – for tasks, employees and managers.

@Controller
@RequestMapping("/tasks")
public class TaskController {

    private TaskDao taskDao;
    private EmployeeDao employeeDao;
    private ManagerDao managerDao;

    @Autowired
    public void setTaskDao(TaskDao taskDao) {
        this.taskDao = taskDao;
    }

    @Autowired
    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }

    @Autowired
    public void setManagerDao(ManagerDao managerDao) {
        this.managerDao = managerDao;
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public TaskDao getTaskDao() {
        return taskDao;
    }

    public ManagerDao getManagerDao() {
        return managerDao;
    }
}

Let’s handle GET request on /tasks:

    /**
     * Retrieves tasks, puts them in the model and returns corresponding view
     * @param model Model to put tasks to
     * @return tasks/list
     */
    @RequestMapping(method = RequestMethod.GET)
    public String showTasks(Model model) {
        model.addAttribute("tasks", taskDao.list());

        return "tasks/list";
    }

We will place JSPs in tasks subfolder. First is list.jsp for showing all tasks. It does not only iterate through all tasks, but on each task it iterates through employees:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>

<!-- resolve variables -->
<%--@elvariable id="tasks" type="java.util.List<org.timesheet.domain.Task>"--%>

<html>
<head>
    <title>Tasks</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h1>List of tasks</h1>
    <a href="tasks?new">Add new task</a>
    <table cellspacing="5" class="main-table wide">
        <tr>
            <th style="width: 35%;">Description</th>
            <th>Manager</th>
            <th>Employees</th>
            <th>Completed</th>
            <th style="width: 20%;">Details</th>
            <th>Delete</th>
        </tr>
        <c:forEach items="${tasks}" var="task">
            <tr>
                <td>${task.description}</td>
                <td>
                    <a href="managers/${task.manager.id}">${task.manager.name}</a>
                </td>
                <td>
                    <c:forEach items="${task.assignedEmployees}" var="emp">
                        <a href="employees/${emp.id}">${emp.name}</a>
                    </c:forEach>
                </td>
                <td>
                    <div class="delete">
                        <c:choose>
                            <c:when test="${task.completed}">
                                Done
                            </c:when>
                            <c:when test="${!task.completed}">
                                In progress
                            </c:when>
                        </c:choose>
                    </div>
                </td>
                <td>
                    <a href="tasks/${task.id}">Go to page</a>
                </td>
                <td>
                    <sf:form action="tasks/${task.id}" method="delete" cssClass="delete">
                        <input type="submit" value="" class="delete-button" />
                    </sf:form>
                </td>
            </tr>
        </c:forEach>
    </table>

    <br />
    <a href="welcome">Go back</a>
</body>
</html>

Deleting task as usual:

    /**
     * Deletes task with specified ID
     * @param id Task's ID
     * @return redirects to tasks if everything was ok
     * @throws TaskDeleteException When task cannot be deleted
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public String deleteTask(@PathVariable("id") long id) 
            throws TaskDeleteException {

        Task toDelete = taskDao.find(id);
        boolean wasDeleted = taskDao.removeTask(toDelete);

        if (!wasDeleted) {
            throw new TaskDeleteException(toDelete);
        }

        // everything OK, see remaining tasks
        return "redirect:/tasks";
    }

TaskDeleteException:

package org.timesheet.web.exceptions;

import org.timesheet.domain.Task;

/**
 * When task cannot be deleted.
 */
public class TaskDeleteException extends Exception {

    private Task task;

    public TaskDeleteException(Task task) {
        this.task = task;
    }

    public Task getTask() {
        return task;
    }
}

Method for handling this exception:

    /**
     * Handles TaskDeleteException
     * @param e Thrown exception with task that couldn't be deleted
     * @return binds task to model and returns tasks/delete-error
     */
    @ExceptionHandler(TaskDeleteException.class)
    public ModelAndView handleDeleteException(TaskDeleteException e) {
        ModelMap model = new ModelMap();
        model.put("task", e.getTask());
        return new ModelAndView("tasks/delete-error", model);
    }

JSP page jsp/tasks/delete-error.jsp for showing deletion error:

<%--@elvariable id="task" type="org.timesheet.domain.Task"--%>

<html>
<head>
    <title>Cannot delete task</title>
</head>
<body>
    Oops! Resource <a href="${task.id}">${task.description}</a> can not be deleted.

    <p>
        Make sure there are no timesheets assigned on task.
    </p>

    <br /><br /><br />
    <a href="../welcome">Back to main page.</a>
</body>
</html>

Showing task’s detail will be accessed with URI /tasks/{id}. We’ll put in the model both task and unassigned employees that can be added to the task. It’ll be handled like so:

    /**
     * Returns task with specified ID
     * @param id Tasks's ID
     * @param model Model to put task to
     * @return tasks/view
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String getTask(@PathVariable("id") long id, Model model) {
        Task task = taskDao.find(id);
        model.addAttribute("task", task);

        // add all remaining employees
        List<Employee> employees = employeeDao.list();
        Set<Employee> unassignedEmployees = new HashSet<Employee>();

        for (Employee employee : employees) {
            if (!task.getAssignedEmployees().contains(employee)) {
                unassignedEmployees.add(employee);
            }
        }

        model.addAttribute("unassigned", unassignedEmployees);

        return "tasks/view";
    }

Now something slightly more complicated. We would like to show user detail page of the task. On this task we’d like to add/remove employees assigned on it.
First, let’s think of URL. Tasks have assigned employees, so our URL for accessing employee on task will be like this:
/tasks/{id}/employees/{employeeId}
To remove employee, we will simply access this resource with DELETE method, so let’s add method to controller:

    /**
     * Removes assigned employee from task
     * @param taskId Task's ID
     * @param employeeId Assigned employee's ID
     */
    @RequestMapping(value = "/{id}/employees/{employeeId}", method = RequestMethod.DELETE)
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void removeEmployee(
            @PathVariable("id") long taskId,
            @PathVariable("employeeId") long employeeId) {

        Employee employee = employeeDao.find(employeeId);
        Task task = taskDao.find(taskId);

        task.removeEmployee(employee);
        taskDao.update(task);
    }

On the view page (we’ll see that just in moment), we will simply alter DOM model using jQuery and remove assigned employee from list.
Let’s pretend that nothing can go wrong (we have NO_CONTENT response) so employee will always be successfully removed from DB. So we can simply alter that DOM model.

For adding employee, we will have selection list (or combo box) of unassigned employees. When employee is removed we will append this to selection of available employees (he is available again). When employee will be added, we will alter Task with DAO and redirect back to same task (everything will be updated). Here’s code for assigning employee to task:

    /**
     * Assigns employee to tak
     * @param taskId Task's ID
     * @param employeeId Employee's ID (to assign)
     * @return redirects back to altered task: tasks/taskId
     */
    @RequestMapping(value = "/{id}/employees/{employeeId}", method = RequestMethod.PUT)
    public String addEmployee(
            @PathVariable("id") long taskId,
            @PathVariable("employeeId") long employeeId) {

        Employee employee = employeeDao.find(employeeId);
        Task task = taskDao.find(taskId);

        task.addEmployee(employee);
        taskDao.update(task);
        
        return "redirect:/tasks/" + taskId;
    }

And finally, tasks/view.jsp for details of Task. As I mentioned, there is lot of DOM altering so this code might seem little more difficult than usually.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<%--@elvariable id="task" type="org.timesheet.domain.Task"--%>
<%--@elvariable id="unassigned" type="java.util.List<org.timesheet.domain.Employee>"--%>

<html>
<head>
    <title>Task page</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h2>Task info</h2>
    <div id="list">
        <ul>
            <li>
                <label for="description">Description:</label>
                <input name="description" id="description" value="${task.description}"
                       disabled="${task.completed ? 'disabled' : ''}" />
            </li>
            <li>
                <label for="manager">Manager:</label>
                <input name="manager" id="manager" value="${task.manager.name}"
                        disabled="true" />
            </li>
            <li>
                <label for="employees">Employees:</label>
                <table id="employees" class="task-table">
                    <c:forEach items="${task.assignedEmployees}" var="emp">
                        <tr>
                            <sf:form action="${task.id}/employees/${emp.id}" method="delete">
                                <td>
                                    <a href="../employees/${emp.id}" id="href-${emp.id}">${emp.name}</a>
                                </td>
                                <td>
                                    <input type="submit" value="Remove" id="remove-${emp.id}" />
                                    <script src="/timesheet-app/resources/jquery-1.7.1.js"></script>
                                    <script type="text/javascript">
                                        $("#remove-${emp.id}").on("click", function() {
                                            $("#remove-${emp.id}").addClass("hidden");
                                            $("#href-${emp.id}").remove();

                                            // add to list of unassigned
                                            var opt = document.createElement("option");
                                            opt.setAttribute("value", "${emp.id}");
                                            opt.textContent = "${emp.name}";
                                            $("#selected-emp").append(opt);
                                        });
                                    </script>
                                </td>
                            </sf:form>
                        </tr>
                    </c:forEach>
                </table>
            </li>
            <li>
                <label for="unassigned">Unassgined:</label>
                <table id="unassigned" class="task-table">
                    <tr>
                        <sf:form method="put" id="add-form">
                            <td>
                                <select id="selected-emp">
                                    <c:forEach items="${unassigned}" var="uemp">
                                        <option value="${uemp.id}">
                                            ${uemp.name}
                                        </option>
                                    </c:forEach>
                                </select>
                            </td>
                            <td>
                                <input type="submit" value="Add" id="add-employee" />
                                <script src="/timesheet-app/resources/jquery-1.7.1.js"></script>
                                <script type="text/javascript">
                                    $("#add-employee").on("click", function() {
                                        $("#selected-emp").selected().remove();
                                    });
                                </script>
                            </td>
                        </sf:form>
                    </tr>
                </table>
            </li>
        </ul>
    </div>

    <br /><br />
    <a href="../tasks">Go Back</a>

    <script src="/timesheet-app/resources/jquery-1.7.1.js"></script>
    <script type="text/javascript">
        (function() {
            // prepare default form action
            setAddAction();

            // handler for changing action
            $("#selected-emp").on("change", function() {
                setAddAction();
            });

            function setAddAction() {
                var id = $("#selected-emp").val();
                $("#add-form").attr("action", "${task.id}/employees/" + id);
            }
        })();
    </script>
</body>
</html>

As you can observe from the code, we’re again using only HTML + JavaScript. Only thing that is JSP specific is bringing data from model to the page.

OK, now we must be able to create new Task. Let’s prepare our controller for serving form for adding task that will be accessed from /tasks?new:

    /**
     * Creates form for new task.
     * @param model Model to bind to HTML form
     * @return tasks/new
     */
    @RequestMapping(params = "new", method = RequestMethod.GET)
    public String createTaskForm(Model model) {
        model.addAttribute("task", new Task());

        // list of managers to choose from
        List<Manager> managers = managerDao.list();
        model.addAttribute("managers", managers);

        return "tasks/new";
    }

Task consists of name, manager and assigned employees. For scope of this tutorial, I decided not to implement the last one. We will simply generate some employees. If you will ever want to be able to pick employees from some sort of selection list and assign them to task, then please note, that this should be done asynchronously. For that purposes you can map special methods to controller and do AJAX posts for example with jQuery with $.post. I think that would be little too much for this tutorial, but if you’re interested how to use AJAX with Spring, check out this blog post on ajax simplifications in Spring 3.
When we were creating Employees and Managers, we only used primitive types for properties. Now we would like to assign actual Manager instance to task. So we will have to tell Spring how it should convert value from select list (manager’s id) to actual instance. For this we will use custom PropertyEditorSupport facility. Add new org.timesheet.web.editors package and create new class ManagerEditor with following code:

public class ManagerEditor extends PropertyEditorSupport {

    private ManagerDao managerDao;

    public ManagerEditor(ManagerDao managerDao) {
        this.managerDao = managerDao;
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        long id = Long.parseLong(text);
        Manager manager = managerDao.find(id);
        setValue(manager);
    }
}

ManagerEditor will have passed DAO in it’s constructor. It will lookup actual manager by it’s ID and call parent’s setValue.
Spring should now know there is such an editor, so we must register it in our controller. We only need method that has WebDataBinder as parameter and we need to annotate it with @InitBinder annotation like so:

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(Manager.class, new ManagerEditor(managerDao));
    }

And that’s it, Spring now knows how to assign manager to our task directly from form.

Finally code for saving Task. As I said earlier, we will generate some employees to task just before saving it:

    /**
     * Saves new task to the database
     * @param task Task to save
     * @return redirects to tasks
     */
    @RequestMapping(method = RequestMethod.POST)
    public String addTask(Task task) {
        // generate employees
        List<Employee> employees = reduce(employeeDao.list());

        task.setAssignedEmployees(employees);
        taskDao.add(task);

        return "redirect:/tasks";
    }

There is reduce method, which is simple helper method for reducing employees in memory. This is not terribly effective, we could do that rather with more sophisticated query, but for now it’ll do just fine. Also feel free to roll your own reduce logic if you want:

    /**
     * Reduces list of employees to some smaller amount.
     * Simulates user interaction.
     * @param employees Employees to reduced
     * @return New list of some employees from original employees list
     */
    private List<Employee> reduce(List<Employee> employees) {
        List<Employee> reduced = new ArrayList<Employee>();
        Random random = new Random();
        int amount = random.nextInt(employees.size()) + 1;

        // max. five employees
        amount = amount > 5 ? 5 : amount;

        for (int i = 0; i < amount; i++) {
            int randomIdx = random.nextInt(employees.size());
            Employee employee = employees.get(randomIdx);
            reduced.add(employee);
            employees.remove(employee);
        }

        return reduced;
    }

Let’s see tasks/new.jsp page now:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<%--@elvariable id="task" type="org.timesheet.domain.Task"--%>
<%--@elvariable id="managers" type="java.util.List<org.timesheet.domain.Manager"--%>

<html>
<head>
    <title>Add new task</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h2>Add new Task</h2>
    <div id="list">
        <sf:form method="post" action="tasks" commandName="task">
            <ul>
                <li>
                    <label for="description">Description:</label>
                    <input name="description" id="description" value="${task.description}" />
                </li>
                <li>
                    <label for="manager-select">Manager:</label>
                    <sf:select path="manager" id="manager-select">
                        <sf:options items="${managers}" itemLabel="name" itemValue="id" />
                    </sf:select>
                </li>
                <li>
                    Employees will be generated ...
                </li>
                <li>
                    <input type="submit" value="Save">
                </li>
            </ul>
        </sf:form>
    </div>

    <br /><br />
    <a href="tasks">Go Back</a>

</body>
</html>

And of course test for controller:

package org.timesheet.web;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import org.timesheet.DomainAwareBase;
import org.timesheet.domain.Employee;
import org.timesheet.domain.Manager;
import org.timesheet.domain.Task;
import org.timesheet.service.dao.EmployeeDao;
import org.timesheet.service.dao.ManagerDao;
import org.timesheet.service.dao.TaskDao;
import org.timesheet.web.exceptions.TaskDeleteException;

import java.util.Collection;
import java.util.List;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ContextConfiguration(locations = {"/persistence-beans.xml", "/controllers.xml"})
public class TaskControllerTest extends DomainAwareBase {

    private Model model; // used for controller
    
    @Autowired
    private TaskDao taskDao;

    @Autowired
    private ManagerDao managerDao;

    @Autowired
    private EmployeeDao employeeDao;
    
    @Autowired
    private TaskController controller;
    
    @Before
    public void setUp() {
        model = new ExtendedModelMap();    
    }
    
    @After
    public void cleanUp() {
        List<Task> tasks = taskDao.list();
        for (Task task : tasks) {
            taskDao.remove(task);
        }
    }

    @Test
    public void testShowTasks() {
        // prepare some data
        Task task = sampleTask();
        
        // use controller
        String view = controller.showTasks(model);
        assertEquals("tasks/list", view);

        List<Task> listFromDao = taskDao.list();
        Collection<?> listFromModel = (Collection<?>) model.asMap ().get("tasks");

        assertTrue(listFromModel.contains(task));
        assertTrue(listFromDao.containsAll(listFromModel));
    }
    
    @Test
    public void testDeleteTaskOk() throws TaskDeleteException {
        Task task = sampleTask();
        long id = task.getId();

        // delete & assert
        String view = controller.deleteTask(id);
        assertEquals("redirect:/tasks", view);
        assertNull(taskDao.find(id));
    }
    
    @Test(expected = TaskDeleteException.class)
    public void testDeleteTaskThrowsException() throws TaskDeleteException {
        Task task = sampleTask();
        long id = task.getId();
        
        // mock DAO for this call
        TaskDao mockedDao = mock(TaskDao.class);
        when(mockedDao.removeTask(task)).thenReturn(false);

        TaskDao originalDao = controller.getTaskDao();
        try {
            // delete & expect exception
            controller.setTaskDao(mockedDao);
            controller.deleteTask(id);
        } finally {
            controller.setTaskDao(originalDao);
        }
    }
    
    @Test
    public void testHandleDeleteException() {
        Task task = sampleTask();
        TaskDeleteException e = new TaskDeleteException(task);
        ModelAndView modelAndView = controller.handleDeleteException(e);

        assertEquals("tasks/delete-error", modelAndView.getViewName());
        assertTrue(modelAndView.getModelMap().containsValue(task));
    }
    
    @Test
    public void testGetTask() {
        Task task = sampleTask();
        long id = task.getId();

        // get & assert
        String view = controller.getTask(id, model);
        assertEquals("tasks/view", view);
        assertEquals(task, model.asMap().get("task"));
    }
    
    @Test
    public void testRemoveEmployee() {
        Task task = sampleTask();
        long id = task.getAssignedEmployees().get(0).getId();
        controller.removeEmployee(task.getId(), id);

        // task was updated inside controller in other transaction -> refresh
        task = taskDao.find(task.getId());

        // get employee & assert
        Employee employee = employeeDao.find(id);
        assertFalse(task.getAssignedEmployees().contains(employee));
    }
    
    @Test
    public void testAddEmployee() {
        Task task = sampleTask();
        Employee cassidy = new Employee("Butch Cassidy", "Cowboys");
        employeeDao.add(cassidy);
        controller.addEmployee(task.getId(), cassidy.getId());

        // task was updated inside controller in other transaction -> refresh
        task = taskDao.find(task.getId());

        // get employee & assert
        Employee employee = employeeDao.find(cassidy.getId());
        assertTrue(task.getAssignedEmployees().contains(employee));
    }
    
    @Test
    public void testAddTask() {
        Task task = sampleTask();
        
        // save via controller
        String view = controller.addTask(task);
        assertEquals("redirect:/tasks", view);
        
        // task is in DB
        assertEquals(task, taskDao.find(task.getId()));
    }

    private Task sampleTask() {
        Manager manager = new Manager("Jesse James");
        managerDao.add(manager);

        Employee terrence = new Employee("Terrence", "Cowboys");
        Employee kid = new Employee("Sundance Kid", "Cowboys");
        employeeDao.add(terrence);
        employeeDao.add(kid);

        Task task = new Task("Wild West", manager, terrence, kid);
        taskDao.add(task);
        
        return task;
    }
}

That’s it for Tasks. Now let’s create controllers for timesheets. Add basic boilerplate for controller and autowired DAOs that we’ll require:

@Controller
@RequestMapping("/timesheets")
public class TimesheetController {

    private TimesheetDao timesheetDao;
    private TaskDao taskDao;
    private EmployeeDao employeeDao;

    @Autowired
    public void setTimesheetDao(TimesheetDao timesheetDao) {
        this.timesheetDao = timesheetDao;
    }

    @Autowired
    public void setTaskDao(TaskDao taskDao) {
        this.taskDao = taskDao;
    }

    @Autowired
    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }

    public TimesheetDao getTimesheetDao() {
        return timesheetDao;
    }

    public TaskDao getTaskDao() {
        return taskDao;
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }
}

Method for handling GET request on timesheets:

    /**
     * Retrieves timesheets, puts them in the model and returns corresponding view
     * @param model Model to put timesheets to
     * @return timesheets/list
     */
    @RequestMapping(method = RequestMethod.GET)
    public String showTimesheets(Model model) {
        List<Timesheet> timesheets = timesheetDao.list();
        model.addAttribute("timesheets", timesheets);

        return "timesheets/list";
    }

JSPs will be placed in timesheets subfolder. Add list.jsp page, that will basically iterate through Timesheet’s properties and roll deleting form:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>

<!-- resolve variables -->
<%--@elvariable id="timesheets" type="java.util.List<org.timesheet.domain.Timesheet>"--%>

<html>
<head>
    <title>Timesheets</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h1>List of timesheets</h1>
    <a href="timesheets?new">Add new timesheet</a>
    <table cellspacing="5" class="main-table wide">
        <tr>
            <th style="width: 30%">Employee</th>
            <th style="width: 50%">Task</th>
            <th>Hours</th>
            <th>Details</th>
            <th>Delete</th>
        </tr>
        <c:forEach items="${timesheets}" var="ts">
            <tr>
                <td>
                    <a href="employees/${ts.who.id}">${ts.who.name}</a>
                </td>
                <td>
                    <a href="tasks/${ts.task.id}">${ts.task.description}</a>
                </td>
                <td>${ts.hours}</td>
                <td>
                    <a href="timesheets/${ts.id}">Go to page</a>
                </td>
                <td>
                    <sf:form action="timesheets/${ts.id}" method="delete" cssClass="delete">
                        <input type="submit" class="delete-button">
                    </sf:form>
                </td>
            </tr>
        </c:forEach>
    </table>

    <br />
    <a href="welcome">Go back</a>
</body>
</html>

Deleting Timesheet is easier than deleting task, because we won’t break any constraint in database, so we can simply use default remove method on DAO:

    /**
     * Deletes timeshet with specified ID
     * @param id Timesheet's ID
     * @return redirects to timesheets
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public String deleteTimesheet(@PathVariable("id") long id) {
        Timesheet toDelete = timesheetDao.find(id);
        timesheetDao.remove(toDelete);

        return "redirect:/timesheets";
    }

We will access individual Timesheet resource by adding it’s ID to URI as usual, so we’ll handle /timesheets/{id}. But there are objects assigned to timesheet – Task instance and Employee instance. We don’t want form to null them out. Therefore we will introduce lightweight command backing object for form. We will update only hours and then set those new hours on real Timesheet instance:

    /**
     * Returns timesheet with specified ID
     * @param id Timesheet's ID
     * @param model Model to put timesheet to
     * @return timesheets/view
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String getTimesheet(@PathVariable("id") long id, Model model) {
        Timesheet timesheet = timesheetDao.find(id);
        TimesheetCommand tsCommand = new TimesheetCommand(timesheet);
        model.addAttribute("tsCommand", tsCommand);

        return "timesheets/view";
    }

And here’s code for TimesheetCommand which is now under new package org.timesheet.web.commands:

package org.timesheet.web.commands;

import org.hibernate.validator.constraints.Range;
import org.timesheet.domain.Timesheet;

import javax.validation.constraints.NotNull;

public class TimesheetCommand {

    @NotNull
    @Range(min = 1, message = "Hours must be 1 or greater")
    private Integer hours;
    private Timesheet timesheet;

    // default c-tor for bean instantiation
    public TimesheetCommand() {}

    public TimesheetCommand(Timesheet timesheet) {
        hours = timesheet.getHours();
        this.timesheet = timesheet;
    }

    public Integer getHours() {
        return hours;
    }

    public void setHours(Integer hours) {
        this.hours = hours;
    }

    public Timesheet getTimesheet() {
        return timesheet;
    }

    public void setTimesheet(Timesheet timesheet) {
        this.timesheet = timesheet;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        TimesheetCommand that = (TimesheetCommand) o;

        if (hours != null ? !hours.equals(that.hours) : that.hours != null) {
            return false;
        }
        if (timesheet != null ? !timesheet.equals(that.timesheet) : that.timesheet != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = hours != null ? hours.hashCode() : 0;
        result = 31 * result + (timesheet != null ? timesheet.hashCode() : 0);
        return result;
    }
}

Pretty straightforward, but what are those @NotNull and @Range annotations? Well, we certailny don’t want user to enter negative or zero number for amount of hours, so we will use this neat JSR 303 Bean Validation API. To make it work, simply add dependency to hibernate validator to your pom.xml:

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>4.2.0.Final</version>
        </dependency>

Once Hibernate Validator is in our classpath, default validator will automatically be picked. To make it work though, we must enable annotation driven MVC, so add following line to timesheet-servlet.xml bean config file:

<mvc:annotation-driven />

We’ll see usage of a valid model a few lines later.

Under timesheets folder we’ll now create view.jsp page that’ll contain info about single timesheet:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<%--@elvariable id="tsCommand" type="org.timesheet.web.commands.TimesheetCommand"--%>

<html>
<head>
    <title>Timesheet page</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h2>Timesheet info</h2>
    <div id="list">
        <sf:form method="post" modelAttribute="tsCommand">
            <sf:errors path="*" cssClass="errors" element="div" />
            <ul>
                <li>
                    <label for="employeeName">Assigned employee:</label>
                    <a id="employee" href="../employees/${tsCommand.timesheet.who.id}">
                        ${tsCommand.timesheet.who.name}
                    </a>
                </li>
                <li>
                    <label for="task">Task:</label>
                    <a id="task" href="../tasks/${tsCommand.timesheet.task.id}">
                        ${tsCommand.timesheet.task.description}
                    </a>
                </li>
                <li>
                    <label for="hours">Hours:</label>
                    <input name="hours" id="hours" value="${tsCommand.hours}" />
                </li>
                <li>
                    <input type="submit" value="Save" />
                </li>
            </ul>
        </sf:form>
    </div>

    <br /><br />
    <a href="../timesheets">Go Back</a>
</body>
</html>

In this view page we have submit button that’ll trigger POST request on /timesheets/{id} and pass updated model (TimesheetCommand instance in that). So let’s handle this. We will use @Valid annotation, which is part of JSR 303 Bean Validation API which marks object to validate. Also note, that TimesheetCommand must be annotated with @ModelAttribute annotation, because this command is bound to web view. Validation errors are stored in BindingResult object:

    /**
     * Updates timesheet with given ID
     * @param id ID of timesheet to lookup from DB
     * @param tsCommand Lightweight command object with changed hours
     * @return redirects to timesheets
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.POST)
    public String updateTimesheet(@PathVariable("id") long id,
            @Valid @ModelAttribute("tsCommand") TimesheetCommand tsCommand,
            BindingResult result) {

        Timesheet timesheet = timesheetDao.find(id);
        if (result.hasErrors()) {
            tsCommand.setTimesheet(timesheet);
            return "timesheets/view";
        }

        // no errors, update timesheet
        timesheet.setHours(tsCommand.getHours());
        timesheetDao.update(timesheet);

        return "redirect:/timesheets";
    }

For adding, we will have to pick from select menus of existing Tasks and Employees, so we’ll pass list of those when serving new form:

    /**
     * Creates form for new timesheet
     * @param model Model to bind to HTML form
     * @return timesheets/new
     */
    @RequestMapping(params = "new", method = RequestMethod.GET)
    public String createTimesheetForm(Model model) {
        model.addAttribute("timesheet", new Timesheet());
        model.addAttribute("tasks", taskDao.list());
        model.addAttribute("employees", employeeDao.list());
        
        return "timesheets/new";
    }

For showing select lists of Employees and Tasks we again need to create editors for them. We saw this approach earlier, so as before let’s add 2 new editors to our project that use corresponding DAOs:

package org.timesheet.web.editors;

import org.timesheet.domain.Employee;
import org.timesheet.service.dao.EmployeeDao;

import java.beans.PropertyEditorSupport;

/**
 * Will convert ID from combobox to employee's instance.
 */
public class EmployeeEditor extends PropertyEditorSupport {

    private EmployeeDao employeeDao;

    public EmployeeEditor(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        long id = Long.parseLong(text);
        Employee employee = employeeDao.find(id);
        setValue(employee);
    }
}
package org.timesheet.web.editors;

import org.timesheet.domain.Task;
import org.timesheet.service.dao.TaskDao;

import java.beans.PropertyEditorSupport;

public class TaskEditor extends PropertyEditorSupport {

    private TaskDao taskDao;

    public TaskEditor(TaskDao taskDao) {
        this.taskDao = taskDao;
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        long id = Long.parseLong(text);
        Task task = taskDao.find(id);
        setValue(task);
    }
}

We’ll register these editors in TimesheetController initBinder method:

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(Employee.class, new EmployeeEditor(employeeDao));
        binder.registerCustomEditor(Task.class, new TaskEditor(taskDao));
    }

Now we can safely add new.jsp under timesheets folder, because select lists will correctly be populated with data passed in the model:

<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%--@elvariable id="employees" type="java.util.List<org.timesheet.domain.Employee"--%>
<%--@elvariable id="tasks" type="java.util.List<org.timesheet.domain.Task"--%>

<html>
<head>
    <title>Add new timesheet</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h2>Add new Timesheet</h2>
    <div id="list">
        <sf:form method="post" action="timesheets" commandName="timesheet">
            <ul>
                <li>
                    <label for="employees">Pick employee:</label>
                    <sf:select path="who" id="employees">
                        <sf:options items="${employees}" itemLabel="name" itemValue="id" />
                    </sf:select>
                </li>
                <li>
                    <label for="tasks">Pick task:</label>
                    <sf:select path="task" id="tasks">
                        <sf:options items="${tasks}" itemLabel="description" itemValue="id" />
                    </sf:select>
                </li>
                <li>
                    <label for="hours">Hours:</label>
                    <sf:input path="hours" />
                </li>
                <li>
                    <input type="submit" value="Save" />
                </li>
            </ul>
        </sf:form>
    </div>

    <br /><br />
    <a href="timesheets">Go Back</a>
</body>
</html>

Submit button submits POST request on /timesheets path, so we’ll handle this with pretty straightforward controller method:

    /**
     * Saves new Timesheet to the database
     * @param timesheet Timesheet to save
     * @return redirects to timesheets
     */
    @RequestMapping(method = RequestMethod.POST)
    public String addTimesheet(Timesheet timesheet) {
        timesheetDao.add(timesheet);

        return "redirect:/timesheets";
    }

So all timesheet functionalities should be working now, just make sure by using application for a while. Of course, we will also write unit test for TimesheetController now. In test methods testUpdateTimesheetValid and testUpdateTimesheetInValid we are not validating object manually, but we’re mocking validator instead:

package org.timesheet.web;

import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.timesheet.DomainAwareBase;
import org.timesheet.domain.Employee;
import org.timesheet.domain.Manager;
import org.timesheet.domain.Task;
import org.timesheet.domain.Timesheet;
import org.timesheet.service.dao.EmployeeDao;
import org.timesheet.service.dao.ManagerDao;
import org.timesheet.service.dao.TaskDao;
import org.timesheet.service.dao.TimesheetDao;
import org.timesheet.web.commands.TimesheetCommand;

import java.util.Collection;
import java.util.List;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ContextConfiguration(locations = {"/persistence-beans.xml", "/controllers.xml"})
public class TimesheetControllerTest extends DomainAwareBase {
    
    @Autowired
    private TimesheetDao timesheetDao;

    @Autowired
    private EmployeeDao employeeDao;

    @Autowired
    private ManagerDao managerDao;

    @Autowired
    private TaskDao taskDao;

    @Autowired
    private TimesheetController controller;
    
    private Model model; // used for controller

    @Before
    public void setUp() {
        model = new ExtendedModelMap();
    }

    @Test
    public void testShowTimesheets() {
        // prepare some data
        Timesheet timesheet = sampleTimesheet();

        // use controller
        String view = controller.showTimesheets(model);
        assertEquals("timesheets/list", view);

        List<Timesheet> listFromDao = timesheetDao.list();
        Collection<?> listFromModel = (Collection<?>) model.asMap().get("timesheets");

        assertTrue(listFromModel.contains(timesheet));
        assertTrue(listFromDao.containsAll(listFromModel));
    }
    
    @Test
    public void testDeleteTimesheet() {
        // prepare ID to delete
        Timesheet timesheet = sampleTimesheet();
        timesheetDao.add(timesheet);
        long id = timesheet.getId();

        // delete & assert
        String view = controller.deleteTimesheet(id);
        assertEquals("redirect:/timesheets", view);
        assertNull(timesheetDao.find(id));
    }

    @Test
    public void testGetTimesheet() {
        // prepare timesheet
        Timesheet timesheet = sampleTimesheet();
        timesheetDao.add(timesheet);
        long id = timesheet.getId();
        TimesheetCommand tsCommand = new TimesheetCommand(timesheet);

        // get & assert
        String view = controller.getTimesheet(id, model);
        assertEquals("timesheets/view", view);
        assertEquals(tsCommand, model.asMap().get("tsCommand"));
    }

    @Test
    public void testUpdateTimesheetValid() {
        // prepare ID to delete
        Timesheet timesheet = sampleTimesheet();
        timesheetDao.add(timesheet);
        long id = timesheet.getId();
        TimesheetCommand tsCommand = new TimesheetCommand(timesheet);

        // user alters Timesheet hours in HTML form with valid value
        tsCommand.setHours(1337);
        BindingResult result = mock(BindingResult.class);
        when(result.hasErrors()).thenReturn(false);

        // update & assert
        String view = controller.updateTimesheet(id, tsCommand, result);
        assertEquals("redirect:/timesheets", view);
        assertTrue(1337 == timesheetDao.find(id).getHours());
    }

    @Test
    public void testUpdateTimesheetInValid() {
        // prepare ID to delete
        Timesheet timesheet = sampleTimesheet();
        timesheetDao.add(timesheet);
        long id = timesheet.getId();

        TimesheetCommand tsCommand = new TimesheetCommand(timesheet);
        Integer originalHours = tsCommand.getHours();

        // user alters Timesheet hours in HTML form with valid value
        tsCommand.setHours(-1);
        BindingResult result = mock(BindingResult.class);
        when(result.hasErrors()).thenReturn(true);

        // update & assert
        String view = controller.updateTimesheet(id, tsCommand, result);
        assertEquals("timesheets/view", view);
        assertEquals(originalHours, timesheetDao.find(id).getHours());
    }

    @Test
    public void testAddTimesheet() {
        // prepare timesheet
        Timesheet timesheet = sampleTimesheet();

        // save but via controller
        String view = controller.addTimesheet(timesheet);
        assertEquals("redirect:/timesheets", view);

        // timesheet is stored in DB
        assertEquals(timesheet, timesheetDao.find(timesheet.getId()));
    }

    private Timesheet sampleTimesheet() {
        Employee marty = new Employee("Martin Brodeur", "NHL");
        employeeDao.add(marty);

        Manager jeremy = new Manager("Jeremy");
        managerDao.add(jeremy);

        Task winStanleyCup = new Task("NHL finals", jeremy, marty);
        taskDao.add(winStanleyCup);

        Timesheet stanelyCupSheet = new Timesheet(marty, winStanleyCup, 100);
        timesheetDao.add(stanelyCupSheet);

        return stanelyCupSheet;
    }
}

Last controller we have to do is for our special business service – TimesheetService. We already have implemented and tested it’s logic. Controller will simply merge this functionalities to one menu page and we’ll handle each with controller. So let’s add some boilerplate controller definition and DAO wiring at first:

@Controller
@RequestMapping("/timesheet-service")
public class TimesheetServiceController {

    private TimesheetService service;
    private EmployeeDao employeeDao;
    private ManagerDao managerDao;

    @Autowired
    public void setService(TimesheetService service) {
        this.service = service;
    }

    @Autowired
    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }

    @Autowired
    public void setManagerDao(ManagerDao managerDao) {
        this.managerDao = managerDao;
    }

}

When user enters /timesheet-service with GET request, we will server him menu with populated data:

    /**
     * Shows menu of timesheet service: 
     * that contains busiest task and employees and managers to
     * look for their assigned tasks.
     * @param model Model to put data to
     * @return timesheet-service/list
     */
    @RequestMapping(method = RequestMethod.GET)
    public String showMenu(Model model) {
        model.addAttribute("busiestTask", service.busiestTask());
        model.addAttribute("employees", employeeDao.list());
        model.addAttribute("managers", managerDao.list());

        return "timesheet-service/menu";
    }

Again, to make stuff work from select lists we will register editors (we’ll just reuse editors that we created recently):

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(Employee.class, new EmployeeEditor(employeeDao));
        binder.registerCustomEditor(Manager.class, new ManagerEditor(managerDao));
    }

Now we will be providing a service. We will have RESTful URLs once again, but actual resources won’t be directly mapped to domain models as before, but results of some internal service. So getting tasks for manager with id 123 will result to GET request timesheets/manager-tasks/123. Same for tasks for employee. We will form actual URLs with jQuery using listeners for select lists. Add timesheet-service folder and add there menu.jsp page with following content:

<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%--@elvariable id="busiestTask" type="org.timesheet.domain.Task"--%>
<%--@elvariable id="managers" type="java.util.List<org.timesheet.domain.Manager>"--%>
<%--@elvariable id="employees" type="java.util.List<org.timesheet.domain.Employee>"--%>

<html>
<head>
    <title>Timesheet Service</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h1>Timesheet services</h1>
    <div id="list">
        <h3>Busiest task</h3>
        <ul>
            <li>
                <a href="/timesheet-app/tasks/${busiestTask.id}"
                   id="busiest-task">${busiestTask.description}</a>
            </li>
        </ul>

        <h3>Tasks for manager</h3>
        <sf:form method="get" id="manager-form">
            <ul>
                <li>
                    <select id="select-managers">
                        <c:forEach items="${managers}" var="man">
                            <option value="${man.id}">${man.name}</option>
                        </c:forEach>
                    </select>
                </li>
                <li>
                    <input type="submit" value="Search" />
                </li>
            </ul>
        </sf:form>

        <h3>Tasks for employee</h3>
        <sf:form method="get" id="employee-form">
            <ul>
                <li>
                    <select id="select-employees">
                        <c:forEach items="${employees}" var="emp">
                            <option value="${emp.id}">${emp.name}</option>
                        </c:forEach>
                    </select>
                </li>
                <li>
                    <input type="submit" value="Search">
                </li>
            </ul>
        </sf:form>
    </div>

    <br /><br />
    <a href="/timesheet-app/welcome">Go Back</a>

    <script src="/timesheet-app/resources/jquery-1.7.1.js"></script>
    <script type="text/javascript">
        (function() {
            // set default actions
            setAddAction("#select-managers", "#manager-form", "manager-tasks");
            setAddAction("#select-employees", "#employee-form", "employee-tasks");

            // handler for chaning action
            $("#select-managers").on("change", function() {
                setAddAction("#select-managers", "#manager-form", "manager-tasks");
            });
            $("#select-employees").on("change", function() {
                setAddAction("#select-employees", "#employee-form", "employee-tasks");
            });

            function setAddAction(selectName, formName, action) {
                var id = $(selectName).val();
                $(formName).attr("action",
                        "/timesheet-app/timesheet-service/" + action + "/" + id);
            }
        })();
    </script>
</body>
</html>

Getting tasks for given manager:

    /**
     * Returns tasks for given manager
     * @param id ID of manager
     * @param model Model to put tasks and manager
     * @return timesheet-service/manager-tasks
     */
    @RequestMapping(value = "/manager-tasks/{id}", method = RequestMethod.GET)
    public String showManagerTasks(@PathVariable("id") long id, Model model) {
        Manager manager = managerDao.find(id);
        List<Task> tasks = service.tasksForManager(manager);

        model.addAttribute("manager", manager);
        model.addAttribute("tasks", tasks);

        return "timesheet-service/manager-tasks";
    }

And as a result page timesheet-service/manager-tasks.jsp will be rendered:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%--@elvariable id="manager" type="org.timesheet.domain.Manager"--%>
<%--@elvariable id="tasks" type="java.util.List<org.timesheet.domain.Task>"--%>

<html>
<head>
    <title>Tasks for manager</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h3>
        Current manager: <a href="/timesheet-app/managers/${manager.id}">${manager.name}</a>
    </h3>
    <div id="list">
        <c:forEach items="${tasks}" var="task">
            <li>
                <a href="/timesheet-app/tasks/${task.id}">${task.description}</a>
            </li>
        </c:forEach>
    </div>

    <br /><br />
    <a href="../">Go Back</a>
</body>
</html>

We’ll do pretty much the same for employee:

    /**
     * Returns tasks for given employee
     * @param id ID of employee
     * @param model Model to put tasks and employee
     * @return timesheet-service/employee-tasks
     */
    @RequestMapping(value = "/employee-tasks/{id}", method = RequestMethod.GET)
    public String showEmployeeTasks(@PathVariable("id") long id, Model model) {
        Employee employee = employeeDao.find(id);
        List<Task> tasks = service.tasksForEmployee(employee);
        
        model.addAttribute("employee", employee);
        model.addAttribute("tasks", tasks);
        
        return "timesheet-service/employee-tasks";
    }

And jsp view employee-tasks.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%--@elvariable id="employee" type="org.timesheet.domain.Employee"--%>
<%--@elvariable id="tasks" type="java.util.List<org.timesheet.domain.Task>"--%>

<html>
<head>
    <title>Tasks for employee</title>
    <link rel="stylesheet" href="/timesheet-app/resources/style.css" type="text/css">
</head>
<body>
    <h3>
        Current employee: <a href="/timesheet-app/employees/${employee.id}">${employee.name}</a>
    </h3>
    <div id="list">
        <c:forEach items="${tasks}" var="task">
            <li>
                <a href="/timesheet-app/tasks/${task.id}">${task.description}</a>
            </li>
        </c:forEach>
    </div>

    <br /><br />
    <a href="../">Go Back</a>
</body>
</html>

So lets make sure everything is well integrated and add unit test for this new contoller:

package org.timesheet.web;

import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.jdbc.SimpleJdbcTestUtils;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.timesheet.DomainAwareBase;
import org.timesheet.domain.Employee;
import org.timesheet.domain.Manager;
import org.timesheet.service.TimesheetService;
import org.timesheet.service.dao.EmployeeDao;
import org.timesheet.service.dao.ManagerDao;

import static org.junit.Assert.assertEquals;

/**
 * This test relies on fact that DAOs and Services are tested individually.
 * Only compares, if controller returns the same as individual services.
 */
@ContextConfiguration(locations = {"/persistence-beans.xml", "/controllers.xml"})
public class TimesheetServiceControllerTest extends DomainAwareBase {

    @Autowired
    private TimesheetServiceController controller;

    @Autowired
    private TimesheetService timesheetService;

    @Autowired
    private EmployeeDao employeeDao;

    @Autowired
    private ManagerDao managerDao;

    @Autowired
    private SimpleJdbcTemplate jdbcTemplate;

    private Model model;
    private final String createScript = "src/main/resources/sql/create-data.sql";

    @Before
    public void setUp() {
        model = new ExtendedModelMap();
        SimpleJdbcTestUtils.executeSqlScript(jdbcTemplate,
                new FileSystemResource(createScript), false);
    }

    @Test
    public void testShowMenu() {
        String view = controller.showMenu(model);
        assertEquals("timesheet-service/menu", view);
        assertEquals(timesheetService.busiestTask(),
                model.asMap().get("busiestTask"));

        // this should be done only on small data sample
        // might cause serious performance cost for complete
        assertEquals(employeeDao.list(), model.asMap().get("employees"));
        assertEquals(managerDao.list(), model.asMap().get("managers"));
    }

    @Test
    public void testShowManagerTasks() {
        // prepare some ID
        Manager manager = managerDao.list().get(0);
        long id = manager.getId();

        String view = controller.showManagerTasks(id, model);
        assertEquals("timesheet-service/manager-tasks", view);
        assertEquals(manager, model.asMap().get("manager"));
        assertEquals(timesheetService.tasksForManager(manager),
                model.asMap().get("tasks"));
    }

    @Test
    public void testShowEmployeeTasks() {
        // prepare some ID
        Employee employee = employeeDao.list().get(0);
        long id = employee.getId();

        String view = controller.showEmployeeTasks(id, model);
        assertEquals("timesheet-service/employee-tasks", view);
        assertEquals(employee, model.asMap().get("employee"));
        assertEquals(timesheetService.tasksForEmployee(employee),
                model.asMap().get("tasks"));
    }
}

Project structure after this part (all new stuff is visible):
Structure of src and test folders
Structure of web folder

Final request mappings:

Request mappings

In the next part of this tutorial we’ll add some support for AOP.

Back to main tutorial page