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.
03_idea_autowiring.png
Or you can also see autowired depencies together with those declared in XML in dependencies view.
03_idea_dependencies.png

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:
03_idea_hql.png

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:
03_idea_structure_src.png
03_idea_structure_test.png

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

Advertisements

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.
02_uml.png

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.

02_select_namespaces.png

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.

02_beans_dependencies.png

<?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.
01_project_structure.png

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.
00_view_source.png

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>
<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.
        Make sure there are no timesheets assigned on task.


<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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):
05_idea_structure_src.png
05_idea_web.png

Final request mappings:

05_final_mappings.png

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

Back to main tutorial page