// $HeadURL:
// http://seanderickson1@forge.abcd.harvard.edu/svn/screensaver/branches/iccbl/data-sharing-levels/src/edu/harvard/med/screensaver/io/screenresults/ScreenResultImporter.java
// $
// $Id$
//
// Copyright © 2006, 2010, 2011, 2012 by the President and Fellows of Harvard College.
//
// Screensaver is an open-source project developed by the ICCB-L and NSRB labs
// at Harvard Medical School. This software is distributed under the terms of
// the GNU General Public License.
package edu.harvard.med.iccbl.screensaver.io.users;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.List;
import java.util.Scanner;
import javax.mail.MessagingException;
import com.google.common.collect.Lists;
import org.apache.commons.cli.OptionBuilder;
import org.apache.log4j.Logger;
import org.joda.time.LocalDate;
import edu.harvard.med.iccbl.screensaver.io.AdminEmailApplication;
import edu.harvard.med.iccbl.screensaver.policy.DataSharingLevelMapper;
import edu.harvard.med.iccbl.screensaver.service.users.UserAgreementUpdater;
import edu.harvard.med.screensaver.db.DAOTransaction;
import edu.harvard.med.screensaver.db.DAOTransactionRollbackException;
import edu.harvard.med.screensaver.db.GenericEntityDAO;
import edu.harvard.med.screensaver.model.activities.AdministrativeActivity;
import edu.harvard.med.screensaver.model.screens.ScreenType;
import edu.harvard.med.screensaver.model.users.ChecklistItemEvent;
import edu.harvard.med.screensaver.model.users.ScreeningRoomUser;
import edu.harvard.med.screensaver.model.users.ScreensaverUserRole;
import edu.harvard.med.screensaver.service.OperationRestrictedException;
import edu.harvard.med.screensaver.util.Pair;
/**
* Locates the _not yet expired_ users who have a UA with an activation on or before the date given, and it expires
* them.<br/>
* The date will be 2 years before the current time.<br/>
* see {@link UserAgreementUpdater#findUsersWithOldUserAgreements(LocalDate, boolean, ScreenType)}
*/
public class UserAgreementExpirationUpdater extends AdminEmailApplication
{
UserAgreementUpdater _userAgreementUpdater = null;
private ScreenType _screenType;
public UserAgreementExpirationUpdater(String[] cmdLineArgs)
{
super(cmdLineArgs);
// NOTE: do NOT put spring bean initialization in the constructor, as this will obviate any database connection settings since it
// forces initialization of ScreensaverProperties before they are set, and the CommandLineArgumentsDatabaseConnectionSettingsResolver uses ScreensaverProperties
//_userAgreementUpdater = (UserAgreementUpdater) getSpringBean("userAgreementUpdater");
}
private void init()
{
_userAgreementUpdater = (UserAgreementUpdater) getSpringBean("userAgreementUpdater");
}
private static Logger log = Logger.getLogger(UserAgreementExpirationUpdater.class);
private static final String EXPIRATION_MESSAGE_TXT_LOCATION = "../../../../../../../userAgreementPrivacyExpirationMessage.txt";
private static final int DEFAULT_EXPIRATION_TIME_DAYS = 730;
public static final String[] SCREEN_TYPE_OPTION =
{
"st",
"screen type",
"screen-type",
"the screen type of the user agreements to be processed (RNAi or Small Molecule)"
};
public static final String[] NOTIFY_DAYS_AHEAD_OPTION =
{
"notifyonlyindays",
"days",
"notify-only-days-ahead",
"specify this option to notify only, # days ahead of the expiration date"
};
public static final String[] EXPIRATION_TIME_OPTION =
{
"expireindays",
"days",
"expire-in-days",
"(optional) time, in days, for the User Agreement to expire (using date of activation value). Default value is " +
DEFAULT_EXPIRATION_TIME_DAYS + " days."
};
public static final String[] EXPIRE_OPTION =
{
"expire",
"",
"expire-user-roles",
"specify this option to expire the users who's User Agreement is dated more than " +
EXPIRATION_TIME_OPTION[LONG_OPTION_INDEX] + " days in the past."
};
public static final String[] EXPIRATION_EMAIL_MESSAGE_LOCATION =
{
"expirationemailfilelocation",
"file location",
"expiration-email-file-location",
"(optional) location of the message to send to users, first line of this text file is the subject, the rest is the email. " +
"Default location is relative to the location of this class, on the classpath: " + EXPIRATION_MESSAGE_TXT_LOCATION
};
public static final String[] TEST_ONLY = {
"testonly", "",
"test-only",
"run the entire operation specified, then roll-back."
};
@SuppressWarnings("static-access")
public static void main(String[] args)
{
final UserAgreementExpirationUpdater app = new UserAgreementExpirationUpdater(args);
app.addCommandLineOption(OptionBuilder.hasArg().isRequired()
.withArgName(SCREEN_TYPE_OPTION[ARG_INDEX])
.withDescription(SCREEN_TYPE_OPTION[DESCRIPTION_INDEX])
.withLongOpt(SCREEN_TYPE_OPTION[LONG_OPTION_INDEX])
.create(SCREEN_TYPE_OPTION[SHORT_OPTION_INDEX]));
app.addCommandLineOption(OptionBuilder.hasArg()
.withArgName(NOTIFY_DAYS_AHEAD_OPTION[ARG_INDEX])
.withDescription(NOTIFY_DAYS_AHEAD_OPTION[DESCRIPTION_INDEX])
.withLongOpt(NOTIFY_DAYS_AHEAD_OPTION[LONG_OPTION_INDEX])
.create(NOTIFY_DAYS_AHEAD_OPTION[SHORT_OPTION_INDEX]));
app.addCommandLineOption(OptionBuilder.hasArg()
.withArgName(EXPIRATION_TIME_OPTION[ARG_INDEX])
.withDescription(EXPIRATION_TIME_OPTION[DESCRIPTION_INDEX])
.withLongOpt(EXPIRATION_TIME_OPTION[LONG_OPTION_INDEX])
.create(EXPIRATION_TIME_OPTION[SHORT_OPTION_INDEX]));
app.addCommandLineOption(OptionBuilder
.withDescription(EXPIRE_OPTION[DESCRIPTION_INDEX])
.withLongOpt(EXPIRE_OPTION[LONG_OPTION_INDEX])
.create(EXPIRE_OPTION[SHORT_OPTION_INDEX]));
app.addCommandLineOption(OptionBuilder.hasArg()
.withArgName(EXPIRATION_EMAIL_MESSAGE_LOCATION[ARG_INDEX])
.withDescription(EXPIRATION_EMAIL_MESSAGE_LOCATION[DESCRIPTION_INDEX])
.withLongOpt(EXPIRATION_EMAIL_MESSAGE_LOCATION[LONG_OPTION_INDEX])
.create(EXPIRATION_EMAIL_MESSAGE_LOCATION[SHORT_OPTION_INDEX]));
app.addCommandLineOption(OptionBuilder
.withDescription(TEST_ONLY[DESCRIPTION_INDEX])
.withLongOpt(TEST_ONLY[LONG_OPTION_INDEX])
.create(TEST_ONLY[SHORT_OPTION_INDEX]));
app.processOptions(true, true);
log.info("==== Running UserAgreementExpirationUpdater: " + app.toString() + "======");
final GenericEntityDAO dao = (GenericEntityDAO) app.getSpringBean("genericEntityDao");
app.init();
dao.doInTransaction(new DAOTransaction() {
public void runTransaction()
{
app.setScreenType(app.getCommandLineOptionEnumValue(SCREEN_TYPE_OPTION[SHORT_OPTION_INDEX], ScreenType.class));
int timeToExpireDays = DEFAULT_EXPIRATION_TIME_DAYS;
if (app.isCommandLineFlagSet(EXPIRATION_TIME_OPTION[SHORT_OPTION_INDEX])) {
timeToExpireDays = app.getCommandLineOptionValue(EXPIRATION_TIME_OPTION[SHORT_OPTION_INDEX], Integer.class);
}
try {
try {
if (app.isCommandLineFlagSet(NOTIFY_DAYS_AHEAD_OPTION[SHORT_OPTION_INDEX]))
{
Integer daysAheadToNotify = app.getCommandLineOptionValue(NOTIFY_DAYS_AHEAD_OPTION[SHORT_OPTION_INDEX],
Integer.class);
app.notifyAhead(daysAheadToNotify, timeToExpireDays);
}
else if (app.isCommandLineFlagSet(EXPIRE_OPTION[SHORT_OPTION_INDEX]))
{
app.expire(timeToExpireDays);
}
else {
app.showHelpAndExit("Must specify either the \"" + NOTIFY_DAYS_AHEAD_OPTION[LONG_OPTION_INDEX] +
"\" option or the \"" +
EXPIRE_OPTION[LONG_OPTION_INDEX] + "\" option");
}
}
catch (OperationRestrictedException e) {
app.sendErrorMail("OperationRestrictedException: Could not complete expiration service", app.toString(), e);
throw new DAOTransactionRollbackException(e);
}
}
catch (MessagingException e) {
String msg = "Admin email operation not completed due to MessagingException";
log.error(msg + ":\nApp: " + app.toString(), e);
throw new DAOTransactionRollbackException(msg, e);
}
if (app.isCommandLineFlagSet(TEST_ONLY[SHORT_OPTION_INDEX])) {
throw new DAOTransactionRollbackException("Rollback, testing only");
}
}
});
log.info("==== finished UserAgreementExpirationUpdater ======");
}
private void setScreenType(ScreenType screenType) //
{
_screenType = screenType;
}
private void notifyAhead(final Integer daysToNotify, final int ageInDays)
throws MessagingException
{
LocalDate expireDate = new LocalDate().plusDays(daysToNotify);
LocalDate maxPerformedDate = expireDate.minusDays(ageInDays);
List<Pair<ScreeningRoomUser,ChecklistItemEvent>> pairList =
_userAgreementUpdater.findUsersWithOldUserAgreements(maxPerformedDate, false, _screenType);
if (pairList.isEmpty()) {
String subject = getMessages().getMessage("admin.users.userAgreementExpiration.warningNotification.noaction.subject", _screenType);
String msg = "No " + _screenType + " Users have agreements (that haven't already been notified) dated earlier than the specified cutoff date: " +
maxPerformedDate
+
", or (now - ageInDaysToExpire + daysToNotify): (" +
new LocalDate() +
" - " +
ageInDays +
"D + " +
daysToNotify + "D).";
sendAdminEmails(subject, msg);
}
else {
// send Admin summary email
String subject = getMessages().getMessage("admin.users.userAgreementExpiration.warningNotification.subject",
pairList.size(),
_screenType,
daysToNotify);
StringBuilder msg =
new StringBuilder(getMessages().getMessage("admin.users.userAgreementExpiration.warningNotification.messageBoilerplate",
pairList.size(),
_screenType,
daysToNotify,
_screenType.getValue()));
if (isAdminEmailOnly()) msg.append("\n----NOTE: sending email only to data sharing level admins ---");
if (isCommandLineFlagSet(TEST_ONLY[SHORT_OPTION_INDEX])) {
subject = "[TEST ONLY, no commits] " + subject;
msg.append("\n----TEST Only: no database changes committed.-------");
}
msg.append("\nUsers: \n");
msg.append(printUserHeader() + "| Checklist Item Date\n");
for (Pair<ScreeningRoomUser,ChecklistItemEvent> pair : pairList) {
msg.append(printUser(pair.getFirst()) + "| " + pair.getSecond().getDatePerformed() + "\n");
}
Pair<String,String> notificationSubjectAndMessage = getExpireNotificationSubjectMessage();
String exampleMessage = MessageFormat.format(notificationSubjectAndMessage.getSecond(),
_screenType.getValue(),
EXPIRE_DATE_FORMATTER.print(expireDate),
"[user's primary data sharing level]" );
msg.append("\n\n[example email]\n");
msg.append("\nSubject: " + notificationSubjectAndMessage.getFirst() + "\n\n");
msg.append(exampleMessage);
sendAdminEmails(subject, msg.toString(), _userAgreementUpdater.findUserAgreementAdmins());
if (isAdminEmailOnly() ||
( isCommandLineFlagSet(TEST_ONLY[SHORT_OPTION_INDEX]) && ! isCommandLineFlagSet(TEST_EMAIL_ONLY[SHORT_OPTION_INDEX]) )) {
for (Pair<ScreeningRoomUser,ChecklistItemEvent> pair : pairList) {
// set the flag so that we don't notify for this CIE again.
_userAgreementUpdater.setLastNotifiedUserAgreementChecklistItemEvent(pair.getFirst(), pair.getSecond(), _screenType);
}
}
else {
// send user an email
for (Pair<ScreeningRoomUser,ChecklistItemEvent> pair : pairList) {
String roleName = "default";
ScreensaverUserRole role = DataSharingLevelMapper.getPrimaryDataSharingLevelRoleForUser(_screenType, pair.getFirst());
if (role != null){
roleName = role.getDisplayableRoleName();
}
String message = MessageFormat.format(notificationSubjectAndMessage.getSecond(),
_screenType,
EXPIRE_DATE_FORMATTER.print(expireDate),
roleName);
if(sendEmail(notificationSubjectAndMessage.getFirst().replace("{0}", _screenType.getValue()), message, pair.getFirst())) {
_userAgreementUpdater.setLastNotifiedUserAgreementChecklistItemEvent(pair.getFirst(), pair.getSecond(), _screenType);
}
}
}
}
}
private void expire(int timeToExpireDays)
throws MessagingException
{
LocalDate expireDate = new LocalDate().minusDays(timeToExpireDays);
List<Pair<ScreeningRoomUser,List<AdministrativeActivity>>> updates = Lists.newLinkedList();
List<Pair<ScreeningRoomUser,ChecklistItemEvent>> pairList =
_userAgreementUpdater.findUsersWithOldUserAgreements(expireDate, true, _screenType);
for (Pair<ScreeningRoomUser,ChecklistItemEvent> pair : pairList) {
updates.add(new Pair<ScreeningRoomUser,List<AdministrativeActivity>>(pair.getFirst(),
_userAgreementUpdater.expireUser(pair.getFirst(), findAdministratorUser(), _screenType)));
}
if (updates.isEmpty()) {
String subject = getMessages().getMessage("admin.users.userAgreementExpiration.expiration.noaction.subject", _screenType);
String msg = "No " + _screenType + " Users have (non expired) agreements dated earlier than the specified cutoff date: " + expireDate;
sendAdminEmails(subject, msg);
}
else {
//Send a summary email to the admin and the recipient list
String subject = getMessages().getMessage("admin.users.userAgreementExpiration.expiration.subject",
updates.size(),
_screenType);
StringBuilder msg =
new StringBuilder(getMessages().getMessage("admin.users.userAgreementExpiration.expiration.messageBoilerplate",
updates.size(),
_screenType,
timeToExpireDays));
if (isCommandLineFlagSet(TEST_ONLY[SHORT_OPTION_INDEX])) {
subject = "Testing: " + subject;
msg.append("\n----TEST Only: no database changes committed.-------");
}
msg.append("\n\n");
for (Pair<ScreeningRoomUser,List<AdministrativeActivity>> result : updates) {
msg.append("\nUser: \n");
msg.append(printUserHeader() + "\n");
msg.append(printUser(result.getFirst()) + "\n");
for (AdministrativeActivity activity : result.getSecond()) {
msg.append("\nComments:" + activity.getComments());
}
msg.append("\n");
}
sendAdminEmails(subject, msg.toString(), _userAgreementUpdater.findUserAgreementAdmins());
}
}
/**
* Return the subject first and the message second.
* Message:
* {0} Expiration Date
*
* @throws MessagingException
*/
private Pair<String,String> getExpireNotificationSubjectMessage() throws MessagingException
{
InputStream in = null;
if (isCommandLineFlagSet(EXPIRATION_EMAIL_MESSAGE_LOCATION[SHORT_OPTION_INDEX])) {
try {
in = new FileInputStream(new File(getCommandLineOptionValue(EXPIRATION_EMAIL_MESSAGE_LOCATION[SHORT_OPTION_INDEX])));
}
catch (FileNotFoundException e) {
sendErrorMail("Operation not completed for UserAgreementExpirationUpdater, could not locate expiration message", toString(), e);
throw new DAOTransactionRollbackException(e);
}
}
else {
in = this.getClass().getResourceAsStream(EXPIRATION_MESSAGE_TXT_LOCATION);
}
Scanner scanner = new Scanner(in);
try {
StringBuilder builder = new StringBuilder();
String subject = scanner.nextLine();
while (scanner.hasNextLine()) {
builder.append(scanner.nextLine()).append("\n");
}
return Pair.newPair(subject, builder.toString());
}
finally {
scanner.close();
}
}
}