uk.ac.ox.oucs.vle.CourseSignupServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for uk.ac.ox.oucs.vle.CourseSignupServiceImpl.java

Source

/*
 * #%L
 * Course Signup Implementation
 * %%
 * Copyright (C) 2010 - 2013 University of Oxford
 * %%
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *             http://opensource.org/licenses/ecl2
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package uk.ac.ox.oucs.vle;

import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import uk.ac.ox.oucs.vle.email.EmailSendingService;
import uk.ac.ox.oucs.vle.email.StateChange;

public class CourseSignupServiceImpl implements CourseSignupService {

    private final static Log log = LogFactory.getLog(CourseSignupServiceImpl.class);

    private CourseDAO dao;
    private SakaiProxy proxy;
    private SearchService searchService;
    private EmailSendingService emailSendingService;
    private NowService now;

    protected final Comparator<CourseGroup> noDateCompatator = new NoDateComparator();

    public void setDao(CourseDAO dao) {
        this.dao = dao;
    }

    public void setProxy(SakaiProxy proxy) {
        this.proxy = proxy;
    }

    public void setSearchService(SearchService searchService) {
        this.searchService = searchService;
    }

    public void setNowService(NowService now) {
        this.now = now;
    }

    public void setEmailSendingService(EmailSendingService emailSendingService) {
        this.emailSendingService = emailSendingService;
    }

    /**
     * 
     */
    public void approve(String signupId) {
        approve(signupId, false, proxy.getCurrentPlacementId());
    }

    public void approve(String signupId, boolean skipAuth, String placementId) {
        CourseSignupDAO signupDao = dao.findSignupById(signupId);
        if (signupDao == null) {
            throw new NotFoundException(signupId);
        }
        CourseGroupDAO groupDao = signupDao.getGroup();
        if (!skipAuth) {
            if (groupDao.getSupervisorApproval()) {
                String currentUserId = proxy.getCurrentUser().getId();
                boolean canApprove = false;
                if (currentUserId.equals(signupDao.getSupervisorId())) {
                    canApprove = true;
                } else {
                    canApprove = isAdministrator(groupDao, currentUserId, canApprove);
                }
                if (!canApprove) {
                    throw new PermissionDeniedException(currentUserId);
                }
            }
        }
        Status oldStatus = signupDao.getStatus();
        signupDao.setStatus(Status.APPROVED);
        signupDao.setAmended(getNow());
        dao.save(signupDao);
        proxy.logEvent(groupDao.getCourseId(), EVENT_SIGNUP, placementId);

        //departmental approval
        boolean departmentApproval = false;
        CourseDepartmentDAO departmentDao = null;
        if (null != signupDao.getDepartment()) {
            departmentDao = dao.findDepartmentByCode(signupDao.getDepartment());
            if (null != departmentDao) {
                if (departmentDao.getApprove()) {
                    if (!departmentDao.getApprovers().isEmpty()) {
                        departmentApproval = true;
                    }
                }
            }
        }

        if (departmentApproval) {
            sendMails(oldStatus, signupId, placementId, emailSendingService);
        } else {
            confirm(signupId, skipAuth, placementId);
        }
    }

    /**
     * 
     */
    public void accept(String signupId) {
        accept(signupId, false, proxy.getCurrentPlacementId());
    }

    public void accept(String signupId, boolean skipAuth, String placementId) {
        CourseSignupDAO signupDao = dao.findSignupById(signupId);
        if (signupDao == null) {
            throw new NotFoundException(signupId);
        }
        CourseGroupDAO groupDao = signupDao.getGroup();
        if (!skipAuth) {
            if (groupDao.getAdministratorApproval()) {
                String currentUserId = proxy.getCurrentUser().getId();
                boolean canAccept = false;
                // If is course admin on one of the components.
                canAccept = isAdministrator(signupDao.getGroup(), currentUserId, canAccept);
                if (!canAccept) {
                    throw new PermissionDeniedException(currentUserId);
                }
            }
        }

        if (!Status.PENDING.equals(signupDao.getStatus()) && !Status.WAITING.equals(signupDao.getStatus())) {
            throw new IllegalStateException("You can only accept signups that are waiting or pending.");
        }
        for (CourseComponentDAO componentDao : signupDao.getComponents()) {
            componentDao.setTaken(componentDao.getTaken() + 1);
            dao.save(componentDao);
        }
        Status oldStatus = signupDao.getStatus();
        signupDao.setStatus(Status.ACCEPTED);
        signupDao.setAmended(getNow());
        dao.save(signupDao);

        // Send emails

        proxy.logEvent(signupDao.getGroup().getCourseId(), EVENT_ACCEPT, placementId);

        if (groupDao.getSupervisorApproval()) {
            sendMails(oldStatus, signupId, placementId, emailSendingService);
        } else {
            approve(signupId, skipAuth, placementId);
        }
    }

    /**
     * 
     */
    public void confirm(String signupId) {
        confirm(signupId, false, proxy.getCurrentPlacementId());
    }

    public void confirm(String signupId, boolean skipAuth, String placementId) {

        String currentUserId = proxy.getCurrentUser().getId();

        CourseSignupDAO signupDao = dao.findSignupById(signupId);
        if (signupDao == null) {
            throw new NotFoundException(signupId);
        }
        CourseGroupDAO groupDao = signupDao.getGroup();

        boolean departmentApproval = false;
        if (null != signupDao.getDepartment()) {
            CourseDepartmentDAO department = dao.findDepartmentByCode(signupDao.getDepartment());
            if (null != department) {
                departmentApproval = department.getApprove();
            }
        }

        if (!skipAuth) {
            if (departmentApproval) {
                List<CourseDepartmentDAO> departments = dao.findApproverDepartments(currentUserId);
                boolean canConfirm = false;
                for (CourseDepartmentDAO dept : departments) {
                    if (dept.getCode().equals(signupDao.getDepartment())) {
                        canConfirm = true;
                        break;
                    }
                }
                if (!canConfirm) {
                    throw new PermissionDeniedException(currentUserId);
                }
            }
        }

        Status oldStatus = signupDao.getStatus();
        signupDao.setStatus(Status.CONFIRMED);
        signupDao.setAmended(getNow());
        dao.save(signupDao);

        proxy.logEvent(groupDao.getCourseId(), EVENT_SIGNUP, placementId);
        sendMails(oldStatus, signupId, placementId, emailSendingService);
    }

    /**
     * 
     */
    public void waiting(String signupId) {
        waiting(signupId, false, proxy.getCurrentPlacementId());
    }

    public void waiting(String signupId, boolean skipAuth, String placementId) {
        CourseSignupDAO signupDao = dao.findSignupById(signupId);
        if (signupDao == null) {
            throw new NotFoundException(signupId);
        }
        CourseGroupDAO groupDao = signupDao.getGroup();
        if (!skipAuth) {
            if (groupDao.getAdministratorApproval()) {
                String currentUserId = proxy.getCurrentUser().getId();
                boolean canAccept = false;
                // If is course admin on one of the components.
                canAccept = isAdministrator(signupDao.getGroup(), currentUserId, canAccept);
                if (!canAccept) {
                    throw new PermissionDeniedException(currentUserId);
                }
            }
        }

        if (!Status.PENDING.equals(signupDao.getStatus())) {
            throw new IllegalStateException("You can only add to waiting list signups that pending.");
        }

        Status oldStatus = signupDao.getStatus();
        signupDao.setStatus(Status.WAITING);
        signupDao.setAmended(getNow());
        dao.save(signupDao);
        proxy.logEvent(signupDao.getGroup().getCourseId(), EVENT_WAITING, placementId);
        sendMails(oldStatus, signupId, placementId, emailSendingService);
    }

    /**
     * 
     */
    public String findSupervisor(String search) {
        // TODO Auto-generated method stub
        return null;
    }

    /**
     * 
     */
    public void setSupervisor(String signupId, String supervisorId) {

        CourseSignupDAO signupDao = dao.findSignupById(signupId);
        if (signupDao == null) {
            throw new NotFoundException(signupId);
        }

        String currentUserId = proxy.getCurrentUser().getId();
        if (!isAdministrator(signupDao.getGroup(), currentUserId, false)) {
            throw new PermissionDeniedException(currentUserId);
        }

        String currentSupervisorId = signupDao.getSupervisorId();
        if (null == currentSupervisorId) {
            signupDao.setSupervisorId(supervisorId);
            dao.save(signupDao);
        }
    }

    /**
     * @inheritDoc
     */
    public void setHideCourse(String courseId, boolean hideGroup) {
        UserProxy user = proxy.getCurrentUser();
        CourseGroupDAO courseGroupDao = dao.findCourseGroupById(courseId);
        if (!isAdministrator(courseGroupDao, user.getId(), false)) {
            throw new PermissionDeniedException(user.getId());
        }
        courseGroupDao.setHideGroup(hideGroup);
        dao.save(courseGroupDao);
        // Re-index. This should be re-factored so that all saves on the CourseGroup
        // cause a re-index to happen.
        searchService.addCourseGroup(new CourseGroupImpl(courseGroupDao, this));
        searchService.tidyUp();
    }

    /**
     * 
     */
    public List<CourseGroup> getAdministering() {
        String userId = proxy.getCurrentUser().getId();
        List<CourseGroupDAO> groupDaos;
        if (proxy.isAdministrator()) {
            groupDaos = dao.findAllGroups();
        } else {
            groupDaos = dao.findAdminCourseGroups(userId);
        }
        List<CourseGroup> groups = new ArrayList<CourseGroup>(groupDaos.size());
        for (CourseGroupDAO groupDao : groupDaos) {
            groups.add(new CourseGroupImpl(groupDao, this));
        }
        return groups;
    }

    /**
     * Get list of course groups for which current user is a teacher
     */
    public List<CourseGroup> getLecturing() {
        String userId = proxy.getCurrentUser().getId();
        List<CourseGroupDAO> groupDaos = dao.findLecturingCourseGroups(userId);
        List<CourseGroup> groups = new ArrayList<CourseGroup>(groupDaos.size());
        for (CourseGroupDAO groupDao : groupDaos) {
            groups.add(new CourseGroupImpl(groupDao, this));
        }
        return groups;
    }

    /**
     * 
     */
    public List<CourseSignup> getPendings() {
        String currentUser = proxy.getCurrentUser().getId();
        List<CourseSignupDAO> signupDaos = dao.findSignupPending(currentUser);
        List<CourseSignup> signups = new ArrayList<CourseSignup>(signupDaos.size());
        for (CourseSignupDAO signupDao : signupDaos) {
            signups.add(new CourseSignupImpl(signupDao, this));
        }
        return signups;
    }

    /**
     * 
     */
    public List<CourseSignup> getApprovals() {
        String currentUser = proxy.getCurrentUser().getId();
        List<CourseSignupDAO> signupDaos = dao.findSignupApproval(currentUser);
        List<CourseSignup> signups = new ArrayList<CourseSignup>(signupDaos.size());
        for (CourseSignupDAO signupDao : signupDaos) {
            signups.add(new CourseSignupImpl(signupDao, this));
        }
        return signups;
    }

    /**
     * 
     */
    public List<CourseSignup> getMySignups(Set<Status> statuses) {
        String userId = proxy.getCurrentUser().getId();
        if (log.isDebugEnabled()) {
            log.debug("Loading all signups for : " + userId + " of status " + statuses);
        }
        List<CourseSignup> signups = new ArrayList<CourseSignup>();
        for (CourseSignupDAO signupDao : dao.findSignupForUser(userId,
                (statuses == null) ? Collections.<Status>emptySet() : statuses)) {
            signups.add(new CourseSignupImpl(signupDao, this));
        }
        return signups;
    }

    /**
     * 
     */
    public List<CourseSignup> getUserComponentSignups(String userId, Set<Status> statuses) {
        if (log.isDebugEnabled()) {
            log.debug("Loading all signups for : " + userId + " of status " + statuses);
        }
        List<CourseSignup> signups = new ArrayList<CourseSignup>();
        for (CourseSignupDAO signupDao : dao.findSignupForUser(userId,
                (statuses == null) ? Collections.<Status>emptySet() : statuses)) {
            signups.add(new CourseSignupImpl(signupDao, this));
        }
        return signups;
    }

    /**
     * Get a course group with components. The components can be limited by specifying a range to load.
     */
    public CourseGroup getCourseGroup(String courseId, Range range) {
        if (range == null) {
            range = Range.UPCOMING;
        }
        CourseGroupDAO courseGroupDao = dao.findCourseGroupById(courseId);
        CourseGroup courseGroup = null;
        if (courseGroupDao != null) {
            List<CourseComponentDAO> componentDAOs = dao.findCourseComponents(courseId, range, getNow());
            courseGroup = new CourseGroupImpl(courseGroupDao, this, componentDAOs);
        }
        return courseGroup;
    }

    /**
     * 
     */
    public List<CourseSignup> getCourseSignups(String courseId, Set<Status> statuses) {
        // Find all the components and then find all the signups.
        String userId = proxy.getCurrentUser().getId();

        CourseGroupDAO groupDao = dao.findCourseGroupById(courseId);
        if (groupDao == null) {
            return null;
        }
        if (!isAdministrator(groupDao, userId, false) && !isLecturer(groupDao, proxy.getCurrentUser(), false)) {
            throw new PermissionDeniedException(userId);
        }
        List<CourseSignupDAO> signupDaos = dao.findSignupByCourse(userId, courseId, statuses);
        List<CourseSignup> signups = new ArrayList<CourseSignup>(signupDaos.size());
        for (CourseSignupDAO signupDao : signupDaos) {
            signups.add(new CourseSignupImpl(signupDao, this));
        }
        return signups;
    }

    public Integer getCountCourseSignups(String courseId, Set<Status> statuses) {
        CourseGroupDAO groupDao = dao.findCourseGroupById(courseId);
        if (groupDao == null) {
            return null;
        }

        return dao.countSignupByCourse(courseId, statuses, getNow());
    }

    public String getDirectUrl(String courseId) {
        return proxy.getDirectUrl(courseId);
    }

    /**
     * 
     */
    public CourseComponent getCourseComponent(String componentId) {
        CourseComponentDAO componentDao = dao.findCourseComponent(componentId);
        return new CourseComponentImpl(componentDao);
    }

    public List<CourseComponent> getAllComponents() {
        List<CourseComponentDAO> componentDaos = dao.findAllComponents();
        List<CourseComponent> courseComponents = new ArrayList<CourseComponent>();
        for (CourseComponentDAO componentDao : componentDaos) {
            courseComponents.add(new CourseComponentImpl(componentDao));
        }
        return courseComponents;
    }

    /**
     * 
     */
    public List<CourseSignup> getComponentSignups(String componentId, Set<Status> statuses)
            throws NotFoundException {
        return getComponentSignups(componentId, statuses, null);
    }

    public List<CourseSignup> getComponentSignups(String componentId, Set<Status> statuses, Integer year)
            throws NotFoundException {

        // Permission check.
        CourseComponentDAO componentDao = dao.findCourseComponent(componentId);
        if (componentDao == null) {
            throw new NotFoundException(componentId);
        }
        UserProxy currentUser = proxy.getCurrentUser();
        if (!isAdministrator(componentDao) && !isLecturer(componentDao, currentUser)) {
            throw new PermissionDeniedException(currentUser.getId());
        }
        List<CourseSignupDAO> signupDaos = dao.findSignupByComponent(componentId, statuses, year);
        List<CourseSignup> signups = new ArrayList<CourseSignup>(signupDaos.size());
        for (CourseSignupDAO signupDao : signupDaos) {
            signups.add(new CourseSignupImpl(signupDao, this));
        }
        return signups;
    }

    /**
     * Check if the current user is a member of a set.
     * @param userIds The set of user IDs to check
     * @return <code>true</code> if the user is in the set.
     */
    boolean containsCurrentUser(Set<String> userIds) {
        // This is used by the CourseGroup class
        String currentUserId = proxy.getCurrentUser().getId();
        return userIds.contains(currentUserId);
    }

    private boolean isAdministrator(CourseGroupDAO groupGroup, String currentUserId, boolean defaultValue) {
        if (groupGroup.getAdministrators().contains(currentUserId)) {
            return true;
        }
        UserProxy user = proxy.findUserById(currentUserId);
        if (user != null && user.getEid().equals(this.getDaisyAdmin())) {
            return true;
        }
        if (proxy.isAdministrator()) {
            return true;
        }
        if (groupGroup.getSuperusers().contains(currentUserId)) {
            return true;
        }
        return defaultValue;
    }

    private boolean isLecturer(CourseGroupDAO groupGroup, UserProxy user, boolean defaultValue) {
        for (CourseComponentDAO componentDao : groupGroup.getComponents()) {
            if (isLecturer(componentDao, user)) {
                return true;
            }
        }
        return defaultValue;
    }

    private boolean isAdministrator(CourseComponentDAO componentDao) {
        UserProxy user = proxy.getCurrentUser();
        if (null == user) {
            return false;
        }
        if (user.getEid().equals(this.getDaisyAdmin())) {
            return true;
        }
        if (proxy.isAdministrator()) {
            return true;
        }
        // Check all the CourseGroups
        for (CourseGroupDAO groupDao : componentDao.getGroups()) {
            if (isAdministrator(groupDao, user.getId(), false)) {
                return true;
            }
        }
        return false;
    }

    private boolean isLecturer(CourseComponentDAO componentDao, UserProxy user) {

        if (null == user) {
            return false;
        }
        if (user.getId().equals(componentDao.getTeacher())) {
            return true;
        }
        return false;
    }

    /**
     * 
     */
    public void reject(String signupId) {
        reject(signupId, false, proxy.getCurrentPlacementId());
    }

    public void reject(String signupId, boolean skipAuth, String placementId) {

        String currentUserId = proxy.getCurrentUser().getId();
        CourseSignupDAO signupDao = dao.findSignupById(signupId);
        if (signupDao == null) {
            throw new NotFoundException(signupId);
        }
        CourseSignup signup = new CourseSignupImpl(signupDao, this);

        Status status = signupDao.getStatus();
        if (Status.PENDING.equals(status) || Status.WAITING.equals(status)) { // Rejected at pending or waiting.
            if (!skipAuth) {
                if (!isAdministrator(signupDao.getGroup(), currentUserId, false)) {
                    throw new PermissionDeniedException(currentUserId);
                }
            }
            signupDao.setStatus(Status.REJECTED);
            signupDao.setAmended(getNow());
            dao.save(signupDao);
            proxy.logEvent(signupDao.getGroup().getCourseId(), EVENT_REJECT, placementId);
            sendMails(status, signupId, placementId, emailSendingService);

        } else if (Status.ACCEPTED.equals(status)) { // Rejected at supervisor stage
            if (!skipAuth) {
                if (!isAdministrator(signupDao.getGroup(), currentUserId,
                        currentUserId.equals(signupDao.getSupervisorId()))) {
                    throw new PermissionDeniedException(currentUserId);
                }
            }
            signupDao.setStatus(Status.REJECTED);
            signupDao.setAmended(getNow());
            dao.save(signupDao);
            for (CourseComponentDAO componentDao : signupDao.getComponents()) {
                componentDao.setTaken(componentDao.getTaken() - 1);
                dao.save(componentDao);
            }
            proxy.logEvent(signupDao.getGroup().getCourseId(), EVENT_REJECT, placementId);
            sendMails(Status.ACCEPTED, signupId, placementId, emailSendingService);
        } else if (Status.APPROVED.equals(status)) {// Rejected at approver stage
            if (!skipAuth) {
                boolean isApprover = dao.findDepartmentApprovers(signupDao.getDepartment()).contains(currentUserId);
                if (!isAdministrator(signupDao.getGroup(), currentUserId, isApprover)) {
                    throw new PermissionDeniedException(currentUserId);
                }
            }
            signupDao.setStatus(Status.REJECTED);
            signupDao.setAmended(getNow());
            dao.save(signupDao);
            for (CourseComponentDAO componentDao : signupDao.getComponents()) {
                componentDao.setTaken(componentDao.getTaken() - 1);
                dao.save(componentDao);
            }
            proxy.logEvent(signupDao.getGroup().getCourseId(), EVENT_REJECT, placementId);
            sendMails(Status.ACCEPTED, signupId, placementId, emailSendingService);
        } else {
            throw new IllegalStateException(
                    "You can only reject signups that are WAITING, PENDING, ACCEPTED or APPROVED");
        }
    }

    /**
     * 
     */
    public void setSignupStatus(String signupId, Status newStatus) {
        CourseSignupDAO signupDao = dao.findSignupById(signupId);
        if (signupDao == null) {
            throw new NotFoundException(signupId);
        }
        String currentUserId = proxy.getCurrentUser().getId();
        if (isAdministrator(signupDao.getGroup(), currentUserId, false)) {
            Status currentStatus = signupDao.getStatus();
            if (!currentStatus.equals(newStatus)) { // Check it actually needs changing.
                signupDao.setStatus(newStatus);
                signupDao.setAmended(getNow());
                dao.save(signupDao);
                int spaceAdjustment = (-currentStatus.getSpacesTaken()) + newStatus.getSpacesTaken();
                if (spaceAdjustment != 0) {
                    for (CourseComponentDAO component : signupDao.getComponents()) {
                        component.setTaken(component.getTaken() + spaceAdjustment);
                        dao.save(component);
                    }
                }
            }
        } else {
            throw new PermissionDeniedException(currentUserId);
        }
    }

    /**
     * @inheritDoc
     */
    public CourseSignup signup(String userId, String userName, String userEmail, String courseId,
            Set<String> componentIds, String supervisorId) {

        CourseGroupDAO groupDao = dao.findCourseGroupById(courseId);
        if (groupDao == null) {
            throw new NotFoundException(courseId);
        }

        Set<CourseComponentDAO> componentDaos = loadComponents(componentIds, groupDao);

        // Check the current user is the administrator for all the components.
        String currentUserId = proxy.getCurrentUser().getId();
        if (!isAdministrator(groupDao, currentUserId, false)) {
            throw new PermissionDeniedException(currentUserId);
        }

        // Create the signup.
        UserProxy user = null;
        if (userId == null) {
            // Check for the user by email.
            if (userEmail == null) {
                throw new IllegalArgumentException("If you don't supply a userId you must supply an email address");
            }
            user = proxy.findUserByEmail(userEmail);
            if (user == null) {
                user = proxy.newUser(userName, userEmail);
            }
        } else {
            user = proxy.findUserById(userId);
        }
        CourseSignupDAO signupDao = dao.newSignup(user.getId(), supervisorId, getNow());
        signupDao.setGroup(groupDao);
        signupDao.setStatus(Status.PENDING);
        signupDao.setAmended(getNow());
        Department department = findPracDepartment(user.getPrimaryOrgUnit());
        if (null != department) {
            signupDao.setDepartment(department.getPracCode());
        }
        String signupId = dao.save(signupDao);

        for (CourseComponentDAO componentDao : componentDaos) {
            List<CourseSignupDAO> signupsToRemove = new ArrayList<CourseSignupDAO>();
            for (CourseSignupDAO componentSignupDao : componentDao.getSignups()) {
                if (componentSignupDao.getUserId().equals(user.getId())) {
                    signupsToRemove.add(componentSignupDao);
                }
            }
            Set<CourseSignupDAO> signupDaos = componentDao.getSignups();
            for (CourseSignupDAO removeSignup : signupsToRemove) {
                // If they had already been accepted then decrement the taken count.
                if (removeSignup.getStatus().equals(Status.APPROVED)
                        || removeSignup.getStatus().equals(Status.ACCEPTED)
                        || removeSignup.getStatus().equals(Status.CONFIRMED)) {
                    for (CourseComponentDAO signupComponentDao : removeSignup.getComponents()) {
                        signupComponentDao.setTaken(signupComponentDao.getTaken() - 1);
                        signupComponentDao.getSignups().remove(removeSignup);
                        dao.save(signupComponentDao);
                    }
                }
                signupDaos.remove(removeSignup);
                dao.remove(removeSignup);
            }

            componentDao.getSignups().add(signupDao);
            componentDao.setTaken(componentDao.getTaken() + 1);
            dao.save(componentDao);
        }

        accept(signupId, false, null);
        return new CourseSignupImpl(signupDao, this);
    }

    /**
     * Signup by Student
     */
    public CourseSignup signup(String courseId, Set<String> componentIds, String supervisorEmail, String message,
            String specialReq) throws IllegalStateException, IllegalArgumentException {

        CourseGroupDAO groupDao = dao.findCourseGroupById(courseId);
        if (groupDao == null) {
            throw new NotFoundException(courseId);
        }

        boolean full = false;
        Set<Status> statuses = Collections.singleton(Status.WAITING);
        if (dao.countSignupByCourse(courseId, statuses, getNow()).intValue() > 0) {
            full = true;
        }

        Set<CourseComponentDAO> componentDaos = loadComponents(componentIds, groupDao);

        // Check they are valid as a choice (in signup period (student), not for same component in same term)
        UserProxy user = proxy.getCurrentUser();
        Date now = getNow();
        List<CourseSignupDAO> existingSignups = new ArrayList<CourseSignupDAO>();
        for (CourseComponentDAO componentDao : componentDaos) {
            if (componentDao.getOpens().after(now) || componentDao.getCloses().before(now)) {
                throw new IllegalStateException(
                        "Component isn't currently open: " + componentDao.getPresentationId());
            }
            if ((componentDao.getSize() - componentDao.getTaken()) < 1) {
                full = true;
            }
            for (CourseSignupDAO signupDao : componentDao.getSignups()) {
                // Look for exisiting signups for these components
                if (user.getId().equals(signupDao.getUserId())) {
                    existingSignups.add(signupDao);
                    if (!signupDao.getStatus().equals(Status.WITHDRAWN)) {
                        throw new IllegalStateException("User " + user.getId()
                                + " already has a place on component: " + componentDao.getPresentationId());
                    }
                }
            }
        }

        // Remove all traces of the existing signup.
        for (CourseSignupDAO existingSignup : existingSignups) {
            for (CourseComponentDAO componentDao : existingSignup.getComponents()) {
                componentDao.getSignups().remove(existingSignup);
                dao.save(componentDao);
            }
            dao.remove(existingSignup);
        }
        // Set the supervisor
        String supervisorId = null;
        if (null != supervisorEmail) {
            UserProxy supervisor = proxy.findUserByEmail(supervisorEmail);
            if (supervisor == null) {
                if (groupDao.getSupervisorApproval()) {
                    throw new IllegalArgumentException("Can't find a supervisor with email: " + supervisorEmail);
                }
            } else {
                supervisorId = supervisor.getId();
            }
        }

        // Create the signup.
        CourseSignupDAO signupDao = dao.newSignup(user.getId(), supervisorId, getNow());
        signupDao.setGroup(groupDao);
        if (full) {
            signupDao.setStatus(Status.WAITING);
        } else {
            signupDao.setStatus(Status.PENDING);
        }
        signupDao.setAmended(getNow());
        signupDao.setMessage(message);
        signupDao.setSpecialReq(specialReq);
        Department department = findPracDepartment(user.getPrimaryOrgUnit());
        if (null != department) {
            signupDao.setDepartment(department.getPracCode());
        } else {
            signupDao.setDepartment(null);
        }
        String signupId = dao.save(signupDao);

        // We're going to decrement the places on acceptance.
        for (CourseComponentDAO componentDao : componentDaos) {
            componentDao.getSignups().add(signupDao);
            signupDao.getComponents().add(componentDao); // So when sending out email we know the components.
            dao.save(componentDao);
        }
        proxy.logEvent(groupDao.getCourseId(), EVENT_SIGNUP, null);

        // Now send emails.
        // Old status, new status, signup ID.
        String placementId = proxy.getCurrentPlacementId();
        CourseSignup signup = new CourseSignupImpl(signupDao, this);

        if (full || groupDao.getAdministratorApproval()) {
            sendMails(null, signupId, placementId, emailSendingService);
        } else {
            accept(signupId, false, null);
        }

        return new CourseSignupImpl(signupDao, this);
    }

    /**
     * Find all the components and check they are associated with the supplied course group.
     * @param componentIds The component IDs to load.
     * @param groupDao The course group to check they belong to.
     * @return The loaded components.
     * @throws IllegalArgumentException If there aren't any component IDs or they don't belong to the group.
     * @throws NotFoundException If one of the component IDs couldn't be found.
     */
    private Set<CourseComponentDAO> loadComponents(Set<String> componentIds, CourseGroupDAO groupDao) {
        if (componentIds == null) {
            throw new IllegalArgumentException("You must specify some components to signup to.");
        }

        Set<CourseComponentDAO> componentDaos = new HashSet<CourseComponentDAO>(componentIds.size());
        for (String componentId : componentIds) {
            CourseComponentDAO componentDao = dao.findCourseComponent(componentId);
            if (componentDao != null) {
                componentDaos.add(componentDao);
                if (!componentDao.getGroups().contains(groupDao)) { // Check that the component is actually part of the set.
                    throw new IllegalArgumentException("The component: " + componentId
                            + " is not part of the course: " + groupDao.getCourseId());
                }
            } else {
                throw new NotFoundException(componentId);
            }
        }
        return componentDaos;
    }

    public void withdraw(String signupId) {
        CourseSignupDAO signupDao = dao.findSignupById(signupId);
        if (signupDao == null) {
            throw new NotFoundException(signupId);
        }
        Status oldStatus = signupDao.getStatus();
        if (Status.ACCEPTED.equals(signupDao.getStatus()) || Status.APPROVED.equals(signupDao.getStatus())
                || Status.CONFIRMED.equals(signupDao.getStatus())) {

            for (CourseComponentDAO componentDao : signupDao.getComponents()) {
                componentDao.setTaken(componentDao.getTaken() - 1);
                dao.save(componentDao);
            }
        }
        signupDao.setStatus(Status.WITHDRAWN);
        signupDao.setAmended(getNow());
        dao.save(signupDao);
        proxy.logEvent(signupDao.getGroup().getCourseId(), EVENT_WITHDRAW, null);
        String placementId = proxy.getCurrentPlacementId();

        sendMails(oldStatus, signupId, placementId, emailSendingService);

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String split(String signupId, Set<String> componentPresentationIds) {
        // Check we can find the signup.
        CourseSignupDAO signupDAO = dao.findSignupById(signupId);
        if (signupDAO == null) {
            throw new NotFoundException(signupId);
        }
        String userId = proxy.getCurrentUser().getId();
        if (!isAdministrator(signupDAO.getGroup(), userId, false)) {
            throw new PermissionDeniedException(userId);
        }
        if (componentPresentationIds == null || componentPresentationIds.isEmpty()) {
            throw new IllegalArgumentException("You must specify some componentPresentationIds.");
        }
        if (componentPresentationIds.size() >= signupDAO.getComponents().size()) {
            throw new IllegalArgumentException("You can't specify all the componentPresentationIds in the signup.");
        }

        // This won't affect the take counts as we keep the same number of components and the new signup is in the
        // same status as the existing one.
        CourseSignupDAO newSignup = dao.newSignup(signupDAO.getUserId(), signupDAO.getSupervisorId(), getNow());
        newSignup.setStatus(signupDAO.getStatus());
        newSignup.setGroup(signupDAO.getGroup());
        newSignup.setDepartment(signupDAO.getDepartment());
        newSignup.setMessage(signupDAO.getMessage());
        dao.save(newSignup);

        // Now move the components to the new signup.
        Set<CourseComponentDAO> newSignupComponents = new HashSet<CourseComponentDAO>();
        // We don't modify arguments.
        Set<String> remainingComponentIds = new HashSet<String>(componentPresentationIds);
        for (CourseComponentDAO componentDAO : signupDAO.getComponents()) {
            if (remainingComponentIds.remove(componentDAO.getPresentationId())) {
                newSignupComponents.add(componentDAO);
                componentDAO.getSignups().remove(signupDAO);
                componentDAO.getSignups().add(newSignup);
                dao.save(componentDAO);
            }
        }

        // Check we removed them all, transactions will clear up the mess.
        if (!remainingComponentIds.isEmpty()) {
            throw new IllegalArgumentException(
                    "Some components weren't part of the signup: " + componentPresentationIds);
        }

        return newSignup.getId();
    }

    public List<CourseGroup> getCourseGroupsByDept(String deptId, Range range, boolean externalUser) {
        List<CourseGroupDAO> cgDaos = dao.findCourseGroupByDept(deptId, range, getNow(), externalUser);
        List<CourseGroup> cgs = new ArrayList<CourseGroup>(cgDaos.size());
        for (CourseGroupDAO cgDao : cgDaos) {
            cgs.add(new CourseGroupImpl(cgDao, this));
        }
        return cgs;
    }

    public List<CourseGroup> getCourseGroupsBySubUnit(String subunitId, Range range, boolean externalUser) {
        List<CourseGroupDAO> cgDaos = dao.findCourseGroupBySubUnit(subunitId, range, getNow(), externalUser);
        List<CourseGroup> cgs = new ArrayList<CourseGroup>(cgDaos.size());
        for (CourseGroupDAO cgDao : cgDaos) {
            cgs.add(new CourseGroupImpl(cgDao, this));
        }
        return cgs;
    }

    public List<CourseGroup> getCourseGroupsByComponent(String componentId) {
        List<CourseGroupDAO> cgDaos = dao.findCourseGroupByComponent(componentId);
        List<CourseGroup> cgs = new ArrayList<CourseGroup>(cgDaos.size());
        for (CourseGroupDAO cgDao : cgDaos) {
            cgs.add(new CourseGroupImpl(cgDao, this));
        }
        return cgs;
    }

    public List<SubUnit> getSubUnitsByDept(String deptId) {
        List<Object[]> subNodes = dao.findSubUnitByDept(deptId);
        List<SubUnit> subUnits = new ArrayList<SubUnit>(subNodes.size());
        for (Object[] subNode : subNodes) {
            subUnits.add(new SubUnitImpl((String) subNode[0], (String) subNode[1]));
        }
        return subUnits;
    }

    public Map<String, String> getDepartments() {
        List<CourseDepartmentDAO> subNodes = dao.findAllDepartments();
        Map<String, String> departments = new HashMap<String, String>();
        for (CourseDepartmentDAO departmentDAO : subNodes) {
            departments.put(departmentDAO.getCode(), departmentDAO.getName());
        }
        return departments;
    }

    public Date getNow() {
        return now.getNow();
    }

    /**
     * Loads details about a user.
     * @return The loaded user or null.
     */
    UserProxy loadUser(String id) {
        return proxy.findUserById(id);
    }

    public Department findPracDepartment(String primaryOrgUnit) {
        CourseDepartmentDAO department = dao.findDepartmentByPrimaryOrgUnit(primaryOrgUnit);
        if (null == department) {
            return null;
        }
        return new DepartmentImpl(department);
    }

    /**
     * 
     */
    public boolean isDepartmentCode(String code) {
        return dao.findDepartmentByCode(code) != null;
    }

    public List<CourseGroup> search(String search, Range range, boolean external) {
        String words[] = search.split(" ");
        List<CourseGroupDAO> groupDaos = dao.findCourseGroupByWords(words, range, getNow(), external);
        List<CourseGroup> groups = new ArrayList<CourseGroup>(groupDaos.size());
        for (CourseGroupDAO groupDao : groupDaos) {
            groups.add(new CourseGroupImpl(groupDao, this));
        }
        return groups;
    }

    public CourseSignup getCourseSignup(String signupId) {
        CourseSignupDAO signupDao = dao.findSignupById(signupId);
        if (signupDao == null) {
            return null;
        }

        String currentUserId = proxy.getCurrentUser().getId();
        if (currentUserId.equals(signupDao.getUserId()) || currentUserId.equals(signupDao.getSupervisorId())
                || isAdministrator(signupDao.getGroup(), currentUserId, false)) {
            return new CourseSignupImpl(signupDao, this);
        } else {
            throw new PermissionDeniedException(currentUserId);
        }
    }

    public CourseSignup getCourseSignupAnyway(String signupId) {
        CourseSignupDAO signupDao = dao.findSignupById(signupId);
        if (signupDao == null) {
            return null;
        } else {
            return new CourseSignupImpl(signupDao, this);
        }
    }

    public String[] getCourseSignupFromEncrypted(String encrypted) {

        String string = proxy.uncode(encrypted);
        if (null == string) {
            throw new IllegalStateException("the encryptied string cannot be decyphered");
        }
        return string.split("\\$");
    }

    /**
     * 
     */
    public List<CourseGroup> getCourseCalendar(boolean external, String providerId) {
        String userId = proxy.getCurrentUser().getId();
        List<CourseComponentDAO> componentDaos = dao.findCourseGroupsByCalendar(external, providerId);
        List<CourseGroup> groups = new ArrayList<CourseGroup>();
        for (CourseComponentDAO componentDao : componentDaos) {
            for (CourseGroupDAO groupDao : componentDao.getGroups()) {
                CourseGroupDAO myGroupDao = (CourseGroupDAO) groupDao.clone();
                myGroupDao.setComponents(Collections.singleton(componentDao));
                groups.add(new CourseGroupImpl(myGroupDao, this));
            }
        }
        return groups;
    }

    /**
     * 
     */
    public List<CourseGroup> getCourseNoDates(boolean external, String providerId) {
        String userId = proxy.getCurrentUser().getId();
        List<CourseComponentDAO> componentDaos = dao.findCourseGroupsByNoDates(external, providerId);
        List<CourseGroup> groups = new ArrayList<CourseGroup>();
        for (CourseComponentDAO componentDao : componentDaos) {
            for (CourseGroupDAO groupDao : componentDao.getGroups()) {
                CourseGroupDAO myGroupDao = (CourseGroupDAO) groupDao.clone();
                myGroupDao.setComponents(Collections.singleton(componentDao));
                groups.add(new CourseGroupImpl(myGroupDao, this));
            }
        }

        Collections.sort(groups, noDateCompatator);

        return groups;
    }

    /**
     * @return This returns the EID of the Daisy administrator.
     */
    protected String getDaisyAdmin() {
        return proxy.getConfigParam("daisy.administrator", "admin");
    }

    /**
     * 
     * @return
     */
    public Integer getRecentDays() {
        return proxy.getConfigParam("recent.days", 14);
    }

    @Override
    public CourseDepartmentImpl findDepartmentByCode(String departmentCode) {
        CourseDepartmentDAO departmentDao = dao.findDepartmentByCode(departmentCode);
        if (departmentDao != null) {
            return new CourseDepartmentImpl(departmentDao.getCode(), departmentDao.getApprove(),
                    departmentDao.getApprovers());
        }
        return null;
    }

    @Override
    public List<CourseComponentExport> exportComponentSignups(String componentId, Set<Status> statuses,
            Integer year) {
        List<Map> componentSignups = dao.findComponentSignups(componentId, statuses, year);
        // These should be ordered already
        List<CourseComponentExport> exports = new ArrayList<>();
        CourseComponentExport export = null;
        for (Map map : componentSignups) {
            CourseSignupDAO signupDAO = (CourseSignupDAO) map.get("signup");
            CourseComponentDAO componentDAO = (CourseComponentDAO) map.get("this");
            CourseGroupDAO groupDAO = (CourseGroupDAO) map.get("group");
            if (componentDAO == null) {
                // We don't bail out here so that we still get a partial export.
                log.error(String.format("Failed to get the complete data when exporting %s, %s, %d", componentId,
                        statuses.toString(), year));
                continue;
            }
            CourseComponent component = new CourseComponentImpl(componentDAO);
            if (isAdministrator(componentDAO)) {
                if (export == null || !export.getComponent().equals(component)) {
                    export = new CourseComponentExport(component);
                    exports.add(export);
                }
                if (signupDAO != null && groupDAO != null) {
                    CourseSignup signup = new CourseSignupImpl(signupDAO, this);
                    CourseGroup group = new CourseGroupImpl(groupDAO, this);
                    export.addSignup(new CourseSignupExport(signup, group));
                }
            }
        }
        return exports;
    }

    /**
      * This should get called whenever a signup is changed.
     * @param current The current signup status.
      * @param old The original signup status, can be <code>null</code>.
     * @param signupId The ID of the signup.
     * @param placementId The current placement.
     * @param emailSendingService
     */
    public void sendMails(Status old, String signupId, String placementId,
            EmailSendingService emailSendingService) {
        CourseSignup signup = getCourseSignupAnyway(signupId);
        CourseDepartment department = null;
        if (null != signup.getDepartment()) {
            department = findDepartmentByCode(signup.getDepartment());
        }
        StateChange stateChange = new StateChange(old, signup, department, placementId);
        emailSendingService.applyRules(stateChange);
    }
}