/*
* #%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;
/**
* Module Signup Overnight Jobs
*
* email course administrator if course component is about to close
*
* SESii 16.1 (WL-2273) Automated process for reminder emails to be sent to students
*
* SESii 16.2 Automated email to remind Supervisor and Administrator of any pending approvals
*/
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.util.ResourceLoader;
import uk.ac.ox.oucs.vle.CourseSignupService.Range;
import uk.ac.ox.oucs.vle.CourseSignupService.Status;
public class ModuleImpl implements Module {
private static final Log log = LogFactory.getLog(ModuleImpl.class);
private final static ResourceLoader rb = new ResourceLoader("messages");
private Date lastMidnight;
private Date todayMidnight;
private Date aboutToStart;
/**
* The DAO to update our entries through.
*/
private CourseDAO dao;
/**
* The proxy for getting users.
*/
private SakaiProxy proxy;
private UserPlacementDAO placementDAO;
public void setCourseDao(CourseDAO dao) {
this.dao = dao;
}
public void setProxy(SakaiProxy proxy) {
this.proxy = proxy;
}
public void setPlacementDAO(UserPlacementDAO placementDAO) {
this.placementDAO = placementDAO;
}
/**
*
*/
public void initialise() {
Calendar calendar = new GregorianCalendar();
calendar.set(Calendar.HOUR, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
lastMidnight = (Date)calendar.getTime();
int aboutToCloseDays = getAboutToCloseDays();
Calendar today = (Calendar)calendar.clone();
today.add(Calendar.DATE, aboutToCloseDays);
todayMidnight = (Date) today.getTime();
int aboutToStartDays = getAboutToStartDays();
Calendar tomorrow = (Calendar)calendar.clone();
tomorrow.add(Calendar.DATE, aboutToStartDays);
aboutToStart = (Date) tomorrow.getTime();
log.info("ModuleImpl.initialise");
log.info("Last Midnight ["+
DateFormat.getInstance().format(lastMidnight)+"]");
log.info("Today Midnight ["+
DateFormat.getInstance().format(todayMidnight)+"]");
log.info("About to Start ["+
DateFormat.getInstance().format(aboutToStart)+"]");
log.info("Courses about to Close [close date between "+
DateFormat.getInstance().format(lastMidnight)+" and "+
DateFormat.getInstance().format(todayMidnight)+"]");
log.info("Courses about to Start [start date between "+
DateFormat.getInstance().format(todayMidnight)+" and "+
DateFormat.getInstance().format(aboutToStart)+"]");
}
/**
*
*/
public void update() {
initialise();
String[] words = new String[0];
// This gets all the groups there are as all the values are ignored.
final List<CourseGroupDAO> groups = dao.findCourseGroupByWords(words, Range.ALL, new Date(), false);
modulesClosing(groups);
modulesStarting(groups);
int reminderDays = getReminderDays();
attentionSignups(reminderDays);
}
/**
* Email course administrator if course component is about to close
* @param groups A list of all groups that may have closing components.
*/
private void modulesClosing(final List<CourseGroupDAO> groups) {
for (CourseGroupDAO group : groups) {
final Set<CourseComponentDAO> components = group.getComponents();
final Set<CourseComponentDAO> componentsClosing = new HashSet<CourseComponentDAO>();
for (CourseComponentDAO component : components) {
if (isToday(component.getCloses())) {
// Component is about to close
log.info("Component is about to close ["+
component.getPresentationId()+":"+
DateFormat.getInstance().format(component.getCloses())+":"+
component.getTitle()+"]");
componentsClosing.add(component);
}
}
if (!componentsClosing.isEmpty()) {
for (String administrator : group.getAdministrators()) {
sendModuleClosingEmail(administrator, group, componentsClosing);
}
}
}
}
/**
* SESii 16.1 Automated process for reminder emails to be sent to students
* @param groups
*/
private void modulesStarting(final List<CourseGroupDAO> groups) {
for (CourseGroupDAO group : groups) {
final Set<CourseComponentDAO> components = group.getComponents();
final Set<CourseComponentDAO> componentsStarting = new HashSet<CourseComponentDAO>();
for (CourseComponentDAO component : components) {
if (isAboutToStart(component.getStarts())) {
// Component is about to start
log.info("Component is about to start ["+
component.getPresentationId()+":"+
DateFormat.getInstance().format(component.getStarts())+":"+
component.getTitle()+"]");
componentsStarting.add(component);
}
}
for (CourseComponentDAO component : componentsStarting) {
for (CourseSignupDAO signup : component.getSignups()) {
if (Status.CONFIRMED == signup.getStatus()) {
sendModuleStartingEmail(signup, component);
}
}
}
}
}
/**
* SESii 16.2 Automated email to remind Supervisor and Administrator of any pending approvals
*/
private void attentionSignups(int reminderDays) {
List<CourseSignupDAO> signups = dao.findSignupStillPendingOrAccepted(reminderDays);
log.info("ModuleImpl.attentionSignups ["+signups.size()+"]");
Map<String, Collection<CourseSignupDAO>> supervisors =
new HashMap<String, Collection<CourseSignupDAO>>();
Map<String, Collection<CourseSignupDAO>> administrators =
new HashMap<String, Collection<CourseSignupDAO>>();
for (CourseSignupDAO signup : signups) {
if (Status.ACCEPTED == signup.getStatus()) {
Collection<CourseSignupDAO> set = supervisors.get(signup.getSupervisorId());
if (null == set) {
set = new HashSet<CourseSignupDAO>();
supervisors.put(signup.getSupervisorId(), set);
}
set.add(signup);
}
if (Status.PENDING == signup.getStatus()) {
CourseGroupDAO group = signup.getGroup();
Collection <String> admins = group.getAdministrators();
for (String admin : admins) {
Collection<CourseSignupDAO> set = administrators.get(admin);
if (null == set) {
set = new HashSet<CourseSignupDAO>();
administrators.put(admin, set);
}
set.add(signup);
}
}
}
for (Map.Entry<String, Collection<CourseSignupDAO>> entry : supervisors.entrySet()) {
sendBumpSupervisorEmail(entry.getKey(), entry.getValue());
}
for (Map.Entry<String, Collection<CourseSignupDAO>> entry : administrators.entrySet()) {
sendBumpAdministratorEmail(entry.getKey(), entry.getValue());
}
}
private boolean isToday(Date date) {
if (null != date) {
if (date.after(lastMidnight) && date.before(todayMidnight)) {
return true;
}
}
return false;
}
private boolean isAboutToStart(Date date) {
if (null != date) {
if (date.after(todayMidnight) && date.before(aboutToStart)) {
return true;
}
}
return false;
}
/**
* Sends a reminder email to an adminstrator that a component for one of their modules closes
* @param administrator The administrator ID who should receive the email.
* @param group The course group in question.
* @param components The components that are closing.
*/
private void sendModuleClosingEmail(String administrator, CourseGroupDAO group,
Collection<CourseComponentDAO> components) {
UserProxy recepient = proxy.findUserById(administrator);
if (recepient == null) {
log.warn("Failed to find user for sending email: "+ administrator);
return;
}
String to = recepient.getEmail();
String subject = MessageFormat.format(rb.getString("signup.closing.subject"),
new Object[]{group.getTitle()});
StringBuffer componentDetails = new StringBuffer();
for (CourseComponentDAO component : components) {
componentDetails.append("\n");
componentDetails.append(formatComponent(component));
}
Object[] baseBodyData = new Object[] {
group.getTitle(),
componentDetails.toString(),
proxy.getAdminUrl(proxy.getCurrentPlacementId())
};
Object[] bodyData = baseBodyData;
String body = MessageFormat.format(rb.getString("signup.closing.body"), bodyData);
proxy.sendEmail(to, subject, body);
}
private void sendModuleStartingEmail(
CourseSignupDAO signup,
CourseComponentDAO component) {
UserProxy recepient = proxy.findUserById(signup.getUserId());
if (recepient == null) {
log.warn("Failed to find user for sending email: "+ signup.getUserId());
return;
}
CourseUserPlacementDAO placementDao = placementDAO.findUserPlacement(signup.getUserId());
if (null == placementDao) {
log.warn("Failed to find placement for sending email: "+ signup.getUserId());
return;
}
String placementId = placementDao.getPlacementId();
Object[] data = new Object[] {
signup.getGroup().getTitle(),
new SimpleDateFormat("EEE d MMM yyyy").format(component.getStarts()),
formatComponent(component),
proxy.getMyUrl(placementId)
};
String to = recepient.getEmail();
String subject = MessageFormat.format(rb.getString("signup.starting.subject"), data);
String body = MessageFormat.format(rb.getString("signup.starting.body"), data);
proxy.sendEmail(to, subject, body);
}
private void sendBumpAdministratorEmail(
String administratorId,
Collection <CourseSignupDAO> signups) {
UserProxy recepient = proxy.findUserById(administratorId);
if (recepient == null) {
log.warn("Failed to find user for sending email: "+ administratorId);
return;
}
String to = recepient.getEmail();
String subject = rb.getString("bump.admin.subject");
StringBuffer signupsDetails = new StringBuffer();
for (CourseSignupDAO signup : signups) {
signupsDetails.append(formatSignup(signup));
signupsDetails.append("\n");
}
CourseUserPlacementDAO placementDao = placementDAO.findUserPlacement(administratorId);
if (null == placementDao) {
log.warn("Failed to find placement for sending email: "+ administratorId);
return;
}
String placementId = placementDao.getPlacementId();
String url = proxy.getConfirmUrl(null, placementId);
String advanceUrl = null;//proxy.getAdvanceUrl(signup.getId(), "accept", null);
Object[] bodyData = new Object[] {
signupsDetails.toString(),
url,
advanceUrl
};
String body = MessageFormat.format(rb.getString("bump.admin.body"), bodyData);
proxy.sendEmail(to, subject, body);
}
private void sendBumpSupervisorEmail(
String supervisorId,
Collection<CourseSignupDAO> signups) {
UserProxy recepient = proxy.findUserById(supervisorId);
if (recepient == null) {
log.warn("Failed to find user for sending email: "+ supervisorId);
return;
}
String to = recepient.getEmail();
String subject = rb.getString("bump.supervisor.subject");
StringBuffer signupsDetails = new StringBuffer();
for (CourseSignupDAO signup : signups) {
signupsDetails.append(formatSignup(signup));
signupsDetails.append("\n");
}
CourseUserPlacementDAO placementDao = placementDAO.findUserPlacement(supervisorId);
if (null == placementDao) {
log.warn("Failed to find placement for sending email: "+ supervisorId);
return;
}
String placementId = placementDao.getPlacementId();
String url = proxy.getConfirmUrl(null, placementId);
String advanceUrl = null;//proxy.getAdvanceUrl(signup.getId(), "accept", null);
Object[] bodyData = new Object[] {
signupsDetails.toString(),
url,
advanceUrl
};
String body = MessageFormat.format(rb.getString("bump.supervisor.body"), bodyData);
proxy.sendEmail(to, subject, body);
}
/**
* Produce a summary of a component for sending to users.
* @param component The component to produce a summary of.
* @return A string to summarise the component.
*/
public String formatComponent(CourseComponentDAO component) {
StringBuilder output = new StringBuilder();
output.append(component.getTitle());
// TODO - The sessions should really be an int and we should check > 1
if (component.getSessions() != null && validString(component.getSessions())) {
output.append(String.format(" runs for %s session(s) and", component.getSessions()));
}
output.append(" starts on ");
if (component.getStarts() != null) {
output.append(component.getStarts());
} else {
output.append(component.getStartsText());
}
TermCode termCode = new TermCode(component.getTermcode());
if (termCode.isValid()) {
output.append(" (");
output.append(termCode.getName());
output.append(")");
}
if(validString(component.getTeacherName())) {
output.append("; the teacher is ");
output.append(component.getTeacherName());
}
return output.toString();
}
/**
*
* @param signup
* @return
*/
public String formatSignup(CourseSignupDAO signup) {
StringBuilder output = new StringBuilder();
CourseGroupDAO group = signup.getGroup();
UserProxy student = proxy.findUserById(signup.getUserId());
output.append(student.getDisplayName());
output.append(" for the course ");
output.append(group.getTitle());
output.append(".");
return output.toString();
}
protected int getTodayInt() {
return proxy.getConfigParam("course-signup.site-id", 1);
}
protected int getReminderDays() {
return proxy.getConfigParam("reminder.days", 7);
}
protected int getAboutToStartDays() {
return proxy.getConfigParam("abouttostart.days", 2);
}
protected int getAboutToCloseDays() {
return proxy.getConfigParam("abouttoclose.days", 1);
}
public boolean validString(String string) {
if (null != string && string.trim().length() > 0) {
return true;
}
return false;
}
}