package pl.net.bluesoft.rnd.pt.ext.deadline;
import org.apache.commons.lang3.time.DateUtils;
import org.aperteworkflow.util.liferay.LiferayBridge;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Subqueries;
import org.quartz.*;
import pl.net.bluesoft.rnd.processtool.ProcessToolContext;
import pl.net.bluesoft.rnd.processtool.ProcessToolContextCallback;
import pl.net.bluesoft.rnd.processtool.bpm.ProcessToolBpmSession;
import pl.net.bluesoft.rnd.processtool.bpm.exception.ProcessToolException;
import pl.net.bluesoft.rnd.processtool.model.BpmTask;
import pl.net.bluesoft.rnd.processtool.model.ProcessInstance;
import pl.net.bluesoft.rnd.processtool.model.UserData;
import pl.net.bluesoft.rnd.processtool.model.config.ProcessStateConfiguration;
import pl.net.bluesoft.rnd.processtool.model.processdata.ProcessDeadline;
import pl.net.bluesoft.rnd.processtool.plugins.ProcessToolRegistry;
import pl.net.bluesoft.rnd.pt.ext.bpmnotifications.service.BpmNotificationService;
import pl.net.bluesoft.rnd.pt.ext.bpmnotifications.util.EmailSender;
import pl.net.bluesoft.rnd.pt.ext.sched.service.ProcessToolSchedulerService;
import pl.net.bluesoft.rnd.util.i18n.I18NSource;
import pl.net.bluesoft.rnd.util.i18n.I18NSourceFactory;
import pl.net.bluesoft.util.lang.Collections;
import pl.net.bluesoft.util.lang.Predicate;
import pl.net.bluesoft.util.lang.Strings;
import pl.net.bluesoft.util.lang.Transformer;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
public class DeadlineEngine {
private static final Logger logger = Logger.getLogger(DeadlineEngine.class.getName());
private static final String DEFAULT_PROFILE_NAME = "DefaultDeadLineProfile";
private ProcessToolRegistry registry;
private Properties pluginProperties;
public DeadlineEngine(ProcessToolRegistry registry, Properties pluginProperties) throws SchedulerException {
this.registry = registry;
this.pluginProperties = pluginProperties;
}
public void init() {
registry.getExecutorService().submit(new Runnable() {
@Override
public void run() {
registry.withProcessToolContext(new ProcessToolContextCallback() {
@Override
public void withContext(ProcessToolContext ctx) {
ProcessToolContext.Util.setThreadProcessToolContext(ctx);
try {
ProcessToolBpmSession bpmSession = ctx.getProcessToolSessionFactory().createAutoSession();
Session session = ctx.getHibernateSession();
List<ProcessInstance> instances = loadProcessesWithDeadlines(session);
for (ProcessInstance pi : instances) {
if (bpmSession.isProcessRunning(pi.getInternalId(), ctx)) {
Set<ProcessDeadline> deadlines = pi.findAttributesByClass(ProcessDeadline.class);
Collection<BpmTask> tasks = bpmSession.findProcessTasks(pi, ctx);
for (ProcessDeadline pd : deadlines) {
for (BpmTask task : tasks) {
if (task.getTaskName().equals(pd.getTaskName()) && task.getAssignee() != null) {
scheduleDeadline(pi.getInternalId(), pd);
}
}
}
}
}
}
finally {
ProcessToolContext.Util.removeThreadProcessToolContext();
}
}
});
}
});
}
private List<ProcessInstance> loadProcessesWithDeadlines(Session session) {
DetachedCriteria pdc = DetachedCriteria.forClass(ProcessDeadline.class)
.add(Restrictions.or(Restrictions.eq("alreadyNotified", Boolean.FALSE), Restrictions.isNull("alreadyNotified")))
.createAlias("processInstance", "pi")
.setProjection(Projections.distinct(Projections.property("pi.id")));
return session.createCriteria(ProcessInstance.class)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.add(Subqueries.propertyIn("id", pdc)).list();
}
public void destroy() {
ProcessToolSchedulerService service = getSchedulerService();
service.cancelScheduledJobGroup(HandleDeadlineJob.class.getName());
}
private ProcessToolSchedulerService getSchedulerService() {
return registry.getRegisteredService(ProcessToolSchedulerService.class);
}
private void scheduleDeadline(String processInternalId, ProcessDeadline pd) {
ProcessToolSchedulerService service = getSchedulerService();
Date currentDate = new Date(), dueDate = pd.getDueDate();
if (DateUtils.isSameDay(dueDate, currentDate) || currentDate.before(dueDate) || !pd.isAlreadyProcessed()) {
JobDataMap dataMap = new JobDataMap();
dataMap.put("processInstanceId", processInternalId);
dataMap.put("deadlineAttribute", pd);
dataMap.put("deadlineEngine", this);
String taskName = pd.getTaskName();
String identity = "pi:" + processInternalId + ";task:" + taskName;
JobDetail jobDetail = JobBuilder.newJob(HandleDeadlineJob.class)
.withIdentity(identity, HandleDeadlineJob.class.getName())
.usingJobData(dataMap)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(identity, HandleDeadlineJob.class.getName())
.startAt(dueDate)
.forJob(jobDetail)
.build();
logger.info("Scheduling deadline job handler at: " + dueDate + " for process instance: " + processInternalId
+ " and task name: " + taskName);
service.scheduleJob(jobDetail, trigger);
}
}
public void onProcessStateChange(final BpmTask task, ProcessInstance pi, boolean processInitiated) {
if (pi == null || pi.getId() == null) {
logger.info("Event contained no persistent process instance. Omitting.");
return;
}
String internalId = pi.getInternalId();
logger.info("Processing deadlines for process: " + internalId);
ProcessToolContext ctx = ProcessToolContext.Util.getThreadProcessToolContext();
pi = ctx.getProcessInstanceDAO().getProcessInstance(pi.getId());
if (pi == null) {
throw new ProcessToolException("Unable to find process instance by internal id: " + internalId);
}
Set<ProcessDeadline> deadlines = pi.findAttributesByClass(ProcessDeadline.class);
if (!deadlines.isEmpty()) {
logger.info("Found deadline configurations for process: " + pi.getInternalId());
ProcessToolBpmSession bpmSession = ctx.getProcessToolSessionFactory().createAutoSession();
List<BpmTask> tasks = processInitiated || task == null ? bpmSession.findProcessTasks(pi, ctx) : new ArrayList<BpmTask>() {{
add(task);
}};
if (!tasks.isEmpty()) {
Set<String> taskNames = new HashSet<String>();
Collections.collect(tasks, new Transformer<BpmTask, String>() {
@Override
public String transform(BpmTask obj) {
return obj.getTaskName();
}
}, taskNames);
logger.info("Found tasks for process: " + pi.getInternalId());
for (ProcessDeadline da : deadlines) {
if (taskNames.contains(da.getTaskName())) {
scheduleDeadline(pi.getInternalId(), da);
}
}
}
else {
logger.info("No tasks found for process: " + pi.getInternalId());
}
}
else {
logger.info("No deadlines found for process: " + pi.getInternalId());
}
}
public void handleDeadlineJob(final String processInstanceId, final ProcessDeadline processDeadline) {
registry.withProcessToolContext(new ProcessToolContextCallback() {
@Override
public void withContext(ProcessToolContext ctx) {
ProcessToolContext.Util.setThreadProcessToolContext(ctx);
try {
signalDeadline(ctx, processInstanceId, processDeadline);
}
catch (Exception e) {
logger.log(Level.SEVERE, "Exception while sending deadline notification", e);
}
finally {
ProcessToolContext.Util.removeThreadProcessToolContext();
}
}
});
}
private void signalDeadline(ProcessToolContext ctx, String processInstanceId, ProcessDeadline processDeadline) throws Exception {
ProcessInstance pi = ctx.getProcessInstanceDAO().getProcessInstanceByInternalId(processInstanceId);
ProcessToolBpmSession bpmSession = ctx.getProcessToolSessionFactory().createAutoSession();
List<BpmTask> tasks = bpmSession.findProcessTasks(pi, ctx);
for (BpmTask task : tasks) {
if (task.getTaskName().equals(processDeadline.getTaskName())) {
String assigneeLogin = task.getAssignee();
Map<String, UserData> notifyUsers = prepareUsersForNotification(ctx, assigneeLogin, processDeadline);
// everything is good, unless it’s not
I18NSource messageSource = getI18NSource();
ProcessStateConfiguration st = ctx.getProcessDefinitionDAO().getProcessStateConfiguration(task);
String taskName = messageSource.getMessage(st.getDescription());
for (UserData user : notifyUsers.values()) {
if (processDeadline.getSkipAssignee() != null && processDeadline.getSkipAssignee() && user.getLogin().equals(assigneeLogin)) {
logger.info("Skipping deadline signal for assignee: " + assigneeLogin);
continue;
}
Map dataModel = new HashMap();
dataModel.put("process", pi);
dataModel.put("taskName", taskName);
dataModel.put("processVisibleId", Strings.hasText(pi.getExternalKey()) ? pi.getExternalKey() : pi.getInternalId());
dataModel.put("notifiedUser", user);
dataModel.put("assignedUser", notifyUsers.get(assigneeLogin));
logger.info("Signaling deadline for task: " + task.getTaskName() + " owned by: " + assigneeLogin + ", mailed to: " + user.getLogin());
if(processDeadline.getProfileName()==null){
EmailSender.sendEmail(DEFAULT_PROFILE_NAME, getBpmNotifications(), user.getEmail(), processDeadline.getTemplateName(), dataModel);
}
else{
EmailSender.sendEmail(processDeadline.getProfileName(), getBpmNotifications(), user.getEmail(), processDeadline.getTemplateName(), dataModel);
}
}
processDeadline.setAlreadyNotified(true);
ctx.getHibernateSession().saveOrUpdate(processDeadline);
}
}
}
private BpmNotificationService getBpmNotifications() {
return registry.getRegisteredService(BpmNotificationService.class);
}
private I18NSource getI18NSource() {
String defaultLocale = pluginProperties.getProperty("default.locale");
Locale locale = Strings.hasText(defaultLocale) ? new Locale(defaultLocale) : Locale.getDefault();
return I18NSourceFactory.createI18NSource(locale);
}
private Map<String, UserData> prepareUsersForNotification(ProcessToolContext ctx, String assigneeLogin, ProcessDeadline processDeadline) {
Map<String, UserData> notifyUsers;
List<String> userLogins = new ArrayList<String>();
userLogins.add(assigneeLogin);
if (Strings.hasText(processDeadline.getNotifyUsersWithLogin())) {
for (String login : processDeadline.getNotifyUsersWithLogin().split(",")) {
if (Strings.hasText(login)) {
userLogins.add(login);
}
}
}
notifyUsers = loadUsersAsMap(ctx, userLogins);
if (!notifyUsers.containsKey(assigneeLogin)) {
throw new ProcessToolException("Unable to find task assignee with login: " + assigneeLogin);
}
if (Strings.hasText(processDeadline.getNotifyUsersWithRole())) {
for (String role : processDeadline.getNotifyUsersWithRole().split(",")) {
if (Strings.hasText(role)) {
for (UserData userWithRole : LiferayBridge.getUsersByRole(role)) {
notifyUsers.put(userWithRole.getLogin(), userWithRole);
}
}
}
}
return notifyUsers;
}
private Map<String, UserData> loadUsersAsMap(ProcessToolContext ctx, List<String> userLogins) {
Map<String, UserData> users = ctx.getUserDataDAO().loadUsersByLogin(userLogins);
for (String login : userLogins) {
if (users.get(login) == null) {
UserData user = LiferayBridge.getLiferayUser(login);
if (user == null) {
logger.warning("Unable to find user by login: " + login);
}
else {
users.put(login, user);
}
}
}
return Collections.filterValues(users, new Predicate<UserData>() {
@Override
public boolean apply(UserData input) {
return input != null;
}
});
}
}