package com.constellio.app.modules.tasks.model.managers;
import static com.constellio.app.modules.tasks.TasksEmailTemplates.COMPLETE_TASK;
import static com.constellio.app.modules.tasks.TasksEmailTemplates.DISPLAY_TASK;
import static com.constellio.app.modules.tasks.TasksEmailTemplates.PARENT_TASK_TITLE;
import static com.constellio.app.modules.tasks.TasksEmailTemplates.TASK_ASSIGNED;
import static com.constellio.app.modules.tasks.TasksEmailTemplates.TASK_ASSIGNED_BY;
import static com.constellio.app.modules.tasks.TasksEmailTemplates.TASK_ASSIGNED_ON;
import static com.constellio.app.modules.tasks.TasksEmailTemplates.TASK_DESCRIPTION;
import static com.constellio.app.modules.tasks.TasksEmailTemplates.TASK_DUE_DATE;
import static com.constellio.app.modules.tasks.TasksEmailTemplates.TASK_STATUS;
import static com.constellio.app.modules.tasks.TasksEmailTemplates.TASK_TITLE_PARAMETER;
import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.from;
import static java.util.Arrays.asList;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.constellio.app.modules.tasks.navigation.TasksNavigationConfiguration;
import org.apache.commons.lang.StringUtils;
import org.joda.time.Duration;
import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.constellio.app.modules.tasks.TasksEmailTemplates;
import com.constellio.app.modules.tasks.model.wrappers.Task;
import com.constellio.app.modules.tasks.model.wrappers.structures.TaskReminder;
import com.constellio.app.modules.tasks.services.TasksSchemasRecordsServices;
import com.constellio.app.services.factories.AppLayerFactory;
import com.constellio.data.dao.managers.StatefulService;
import com.constellio.data.threads.BackgroundThreadConfiguration;
import com.constellio.data.threads.BackgroundThreadExceptionHandling;
import com.constellio.data.threads.BackgroundThreadsManager;
import com.constellio.data.utils.TimeProvider;
import com.constellio.model.entities.records.Record;
import com.constellio.model.entities.records.Transaction;
import com.constellio.model.entities.records.wrappers.EmailToSend;
import com.constellio.model.entities.records.wrappers.User;
import com.constellio.model.entities.schemas.Metadata;
import com.constellio.model.entities.schemas.Schemas;
import com.constellio.model.entities.structures.EmailAddress;
import com.constellio.model.services.migrations.ConstellioEIMConfigs;
import com.constellio.model.services.records.RecordServices;
import com.constellio.model.services.records.RecordServicesException;
import com.constellio.model.services.search.SearchServices;
import com.constellio.model.services.search.query.ReturnedMetadatasFilter;
import com.constellio.model.services.search.query.logical.LogicalSearchQuery;
import com.constellio.model.services.search.query.logical.condition.LogicalSearchCondition;
public class TaskReminderEmailManager implements StatefulService {
private static final Logger LOGGER = LoggerFactory.getLogger(TaskReminderEmailManager.class);
static int RECORDS_BATCH = 1000;
private static final long TWENTY_SECONDS = 60 * 1000l;
private static final Duration DURATION_BETWEEN_EXECUTION = new Duration(TWENTY_SECONDS);
public static final String ID = "taskReminderEmailManager";
private final BackgroundThreadsManager backgroundThreadsManager;
private final TasksSchemasRecordsServices taskSchemas;
private final AppLayerFactory appLayerFactory;
private final RecordServices recordServices;
SearchServices searchServices;
ConstellioEIMConfigs eimConfigs;
public TaskReminderEmailManager(AppLayerFactory appLayerFactory, String collection) {
this.appLayerFactory = appLayerFactory;
this.backgroundThreadsManager = appLayerFactory.getModelLayerFactory().getDataLayerFactory()
.getBackgroundThreadsManager();
taskSchemas = new TasksSchemasRecordsServices(collection, appLayerFactory);
searchServices = appLayerFactory.getModelLayerFactory().newSearchServices();
recordServices = appLayerFactory.getModelLayerFactory().newRecordServices();
eimConfigs = new ConstellioEIMConfigs(appLayerFactory.getModelLayerFactory().getSystemConfigurationsManager());
}
@Override
public void initialize() {
configureBackgroundThread();
}
void configureBackgroundThread() {
Runnable sendEmailsAction = new Runnable() {
@Override
public void run() {
generateReminderEmails();
}
};
backgroundThreadsManager.configure(BackgroundThreadConfiguration
.repeatingAction("EmailQueueManager", sendEmailsAction)
.handlingExceptionWith(BackgroundThreadExceptionHandling.CONTINUE)
.executedEvery(DURATION_BETWEEN_EXECUTION));
}
void generateReminderEmails() {
LogicalSearchQuery query = new LogicalSearchQuery(
from(taskSchemas.userTask.schema()).where(taskSchemas.userTask.nextReminderOn())
.isLessOrEqualThan(TimeProvider.getLocalDate()));
do {
query.setNumberOfRows(RECORDS_BATCH);
Transaction transaction = new Transaction();
List<Task> readyToSendTasks = taskSchemas.searchTasks(query);
for (Task task : readyToSendTasks) {
generateReminderEmail(task, transaction);
}
try {
recordServices.execute(transaction);
} catch (RecordServicesException e) {
LOGGER.warn("Batch not processed", e);
}
} while (searchServices.hasResults(query));
}
private void generateReminderEmail(Task task, Transaction transaction) {
List<String> assigneeCandidates = getAssigneeCandidates(task);
List<EmailAddress> validEmailAddresses = getValidEmailAddresses(assigneeCandidates);
if (!validEmailAddresses.isEmpty()) {
EmailToSend emailToSend = createEmailToSend(task, validEmailAddresses);
transaction.add(emailToSend);
} else {
LOGGER.warn("Task reminder not sent because no assignee candidate with valid email " + task.getTitle());
}
task = updateTaskReminders(task);
transaction.add(task);
}
private Task updateTaskReminders(Task task) {
List<TaskReminder> newReminders = new ArrayList<>();
for (TaskReminder taskReminder : task.getReminders()) {
if (taskReminder.computeDate(task).isBefore(TimeProvider.getLocalDate()) || taskReminder.computeDate(task)
.isEqual(TimeProvider.getLocalDate())) {
taskReminder.setProcessed(true);
}
newReminders.add(taskReminder);
}
return task.setReminders(newReminders);
}
private EmailToSend createEmailToSend(Task task, List<EmailAddress> validEmailAddresses) {
List<String> parameters = new ArrayList<>();
EmailToSend emailToSend = taskSchemas.newEmailToSend().setTryingCount(0d)
.setTemplate(TasksEmailTemplates.TASK_REMINDER)
.setTo(validEmailAddresses)
.setParameters(parameters)
.setSendOn(TimeProvider.getLocalDateTime());
prepareTaskParameters(emailToSend, task);
return emailToSend;
}
private void prepareTaskParameters(EmailToSend emailToSend, Task task) {
List<String> newParameters = new ArrayList<>();
List<String> parameters = emailToSend.getParameters();
newParameters.addAll(parameters);
String parentTaskTitle = "";
String assignerFullName = getUserFullNameById(task.getAssigner());
String assigneeFullName = getUserFullNameById(task.getAssignee());
if (task.getParentTask() != null) {
Task parentTask = taskSchemas.getTask(task.getParentTask());
parentTaskTitle = parentTask.getTitle();
}
String status = taskSchemas.getTaskStatus(task.getStatus()).getTitle();
newParameters.add(TASK_TITLE_PARAMETER + ":" + formatToParameter(task.getTitle()));
newParameters.add(PARENT_TASK_TITLE + ":" + formatToParameter(parentTaskTitle));
newParameters.add(TASK_ASSIGNED_BY + ":" + formatToParameter(assignerFullName));
newParameters.add(TASK_ASSIGNED_ON + ":" + formatToParameter(task.getAssignedOn()));
newParameters.add(TASK_ASSIGNED + ":" + formatToParameter(assigneeFullName));
newParameters.add(TASK_DUE_DATE + ":" + formatToParameter(task.getDueDate()));
newParameters.add(TASK_STATUS + ":" + formatToParameter(status));
newParameters.add(TASK_DESCRIPTION + ":" + formatToParameter(task.getDescription()));
String constellioURL = eimConfigs.getConstellioUrl();
newParameters
.add(DISPLAY_TASK + ":" + constellioURL + "#!" + TasksNavigationConfiguration.DISPLAY_TASK + "/" + task.getId());
newParameters.add(COMPLETE_TASK + ":" + constellioURL + "#!" + TasksNavigationConfiguration.EDIT_TASK
+ "/completeTask%253Dtrue%253Bid%253D" + task.getId());
emailToSend.setParameters(newParameters);
}
private Object formatToParameter(Object parameter) {
if(parameter == null) {
return "";
}
return parameter;
}
private String getUserNameById(String userId) {
if (org.apache.commons.lang3.StringUtils.isBlank(userId)) {
return "";
}
return taskSchemas.wrapUser(recordServices.getDocumentById(userId)).getUsername();
}
private String getUserFullNameById(String userId) {
if (org.apache.commons.lang3.StringUtils.isBlank(userId)) {
return "";
}
return taskSchemas.wrapUser(recordServices.getDocumentById(userId)).getFirstName() + " " +
taskSchemas.wrapUser(recordServices.getDocumentById(userId)).getLastName();
}
private List<EmailAddress> getValidEmailAddresses(List<String> usersIds) {
List<EmailAddress> returnList = new ArrayList<>();
LogicalSearchCondition condition = from(taskSchemas.userSchema()).where(Schemas.IDENTIFIER).isIn(usersIds);
Metadata userEmailMetadata = taskSchemas.userSchema().get(User.EMAIL);
List<Record> usersFromGroups = searchServices.search(new LogicalSearchQuery(condition).
setReturnedMetadatas(ReturnedMetadatasFilter.onlyMetadatas(asList(Schemas.TITLE, userEmailMetadata))));
for (Record userRecord : usersFromGroups) {
String userEmail = userRecord.get(userEmailMetadata);
String userTitle = userRecord.get(Schemas.TITLE);
if (!StringUtils.isBlank(userEmail)) {
returnList.add(new EmailAddress(userTitle, userEmail));
} else {
LOGGER.warn("User with blank email " + userTitle);
}
}
return returnList;
}
private List<String> getAssigneeCandidates(Task task) {
Set<String> returnSet = new HashSet<>();
String assignee = task.getAssignee();
if (assignee != null) {
returnSet.add(assignee);
}
List<String> taskAssignationUsersCandidates = task.getAssigneeUsersCandidates();
if (taskAssignationUsersCandidates != null) {
returnSet.addAll(taskAssignationUsersCandidates);
}
List<String> taskAssigneeGroupsCandidates = task.getAssigneeGroupsCandidates();
if (taskAssigneeGroupsCandidates != null && !taskAssigneeGroupsCandidates.isEmpty()) {
Metadata userGroups = taskSchemas.userSchema().getMetadata(User.GROUPS);
LogicalSearchCondition condition = from(taskSchemas.userSchema()).where(userGroups)
.isContaining(taskAssigneeGroupsCandidates);
List<Record> usersFromGroups = searchServices.search(new LogicalSearchQuery(condition)
.setReturnedMetadatas(ReturnedMetadatasFilter.idVersionSchema()));
for (Record userRecord : usersFromGroups) {
returnSet.add(userRecord.getId());
}
}
return new ArrayList<>(returnSet);
}
@Override
public void close() {
}
}