package pl.net.bluesoft.rnd.processtool.dao.impl; import static org.hibernate.criterion.Restrictions.eq; import static org.hibernate.criterion.Restrictions.in; import static pl.net.bluesoft.util.lang.DateUtil.addDays; import static pl.net.bluesoft.util.lang.DateUtil.asCalendar; import static pl.net.bluesoft.util.lang.DateUtil.truncHours; import static pl.net.bluesoft.util.lang.FormatUtil.formatShortDate; import static pl.net.bluesoft.util.lang.FormatUtil.nvl; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.aperteworkflow.search.ProcessInstanceSearchAttribute; import org.aperteworkflow.search.ProcessInstanceSearchData; import org.aperteworkflow.search.SearchProvider; import org.aperteworkflow.search.Searchable; import org.hibernate.Criteria; import org.hibernate.FetchMode; import org.hibernate.Session; import org.hibernate.criterion.CriteriaSpecification; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Order; import org.hibernate.criterion.ProjectionList; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Property; import org.hibernate.criterion.Restrictions; import org.hibernate.criterion.Subqueries; import pl.net.bluesoft.rnd.processtool.dao.ProcessInstanceDAO; import pl.net.bluesoft.rnd.processtool.hibernate.ResultsPageWrapper; import pl.net.bluesoft.rnd.processtool.hibernate.SimpleHibernateBean; import pl.net.bluesoft.rnd.processtool.hibernate.transform.NestedAliasToBeanResultTransformer; import pl.net.bluesoft.rnd.processtool.model.BpmTask; import pl.net.bluesoft.rnd.processtool.model.ProcessInstance; import pl.net.bluesoft.rnd.processtool.model.ProcessInstanceAttachmentAttribute; import pl.net.bluesoft.rnd.processtool.model.ProcessInstanceAttribute; import pl.net.bluesoft.rnd.processtool.model.ProcessInstanceFilter; import pl.net.bluesoft.rnd.processtool.model.ProcessInstanceLog; import pl.net.bluesoft.rnd.processtool.model.UserData; import pl.net.bluesoft.rnd.processtool.model.config.ProcessDefinitionConfig; import pl.net.bluesoft.rnd.processtool.model.config.ProcessDefinitionPermission; import pl.net.bluesoft.rnd.processtool.model.config.ProcessStateConfiguration; import pl.net.bluesoft.rnd.processtool.model.config.ProcessStatePermission; import pl.net.bluesoft.util.lang.Collections; import pl.net.bluesoft.util.lang.Transformer; /** * @author tlipski@bluesoft.net.pl * @author kkolodziej@bluesoft.net.pl */ public class ProcessInstanceDAOImpl extends SimpleHibernateBean<ProcessInstance> implements ProcessInstanceDAO { private SearchProvider searchProvider; public ProcessInstanceDAOImpl(Session session, SearchProvider searchProvider) { super(session); this.searchProvider = searchProvider; } public long saveProcessInstance(ProcessInstance processInstance) { UserData creator = processInstance.getCreator(); if (creator != null) { if (creator.getId() != null) { processInstance.setCreator((UserData) session.get(UserData.class, creator.getId())); } else { List users = session.createCriteria(UserData.class) .add(eq("login", creator.getLogin())).list(); if (users.isEmpty()) { session.saveOrUpdate(creator); } else { processInstance.setCreator((UserData) users.get(0)); } } } if (processInstance.getToDelete() != null) { for (Object o : processInstance.getToDelete()) { session.delete(o); } } Set<ProcessInstanceAttribute> procAttrib = processInstance.getProcessAttributes(); for (ProcessInstanceAttribute attrib:procAttrib){ if (attrib instanceof ProcessInstanceAttachmentAttribute && attrib.getId()!=null){ session.evict(session.get(attrib.getClass(), attrib.getId())); } } session.saveOrUpdate(processInstance); session.flush(); long time = System.currentTimeMillis(); //update search indexes ProcessInstanceSearchData searchData = new ProcessInstanceSearchData(processInstance.getId()); //put some default search attributes if (creator != null) { searchData.addSearchAttribute(new ProcessInstanceSearchAttribute("creator_login", creator.getLogin())); searchData.addSearchAttribute(new ProcessInstanceSearchAttribute("creator_email", creator.getEmail())); searchData.addSearchAttribute(new ProcessInstanceSearchAttribute("creator_realname", creator.getRealName())); } searchData.addSearchAttributes(new String[][]{ {"instance_key", processInstance.getExternalKey()}, {"definition_name", processInstance.getDefinitionName()}, {"instance_description", processInstance.getDescription()}, {"instance_internal_id", processInstance.getInternalId()}, {"instance_keyword", processInstance.getKeyword()}, {"instance_state", processInstance.getState()},//TODO remember about multiple states (when BpmTask is merged) {"instance_create_date", formatShortDate(processInstance.getCreateDate())}, }); ProcessDefinitionConfig def = processInstance.getDefinition(); searchData.addSearchAttributes(new String[][]{ {"definition_key", def.getBpmDefinitionKey()}, {"definition_description", def.getDescription()}, {"definition_comment", def.getComment()}, {"definition_processname", def.getProcessName()}, }); for (ProcessDefinitionPermission perm : def.getPermissions()) { if ("SEARCH".equals(perm.getPrivilegeName())) { String roleName = perm.getRoleName(); if (roleName.equals(".*")) roleName = "__AWF__ROLE_ALL"; roleName = roleName.replace(' ', '_'); searchData.addSearchAttribute("__AWF__ROLE", roleName, true); } } //lookup process state configuration for (BpmTask t : nvl(processInstance.getActiveTasks(), new BpmTask[0])) { ProcessStateConfiguration psc = new ProcessDefinitionDAOImpl(session).getProcessStateConfiguration(t); if (psc != null) { searchData.addSearchAttributes(new String[][]{ {"state_commentary", psc.getCommentary()}, {"state_description", psc.getDescription()}, {"state_name", psc.getName()}, }); for (ProcessStatePermission perm : psc.getPermissions()) { if ("SEARCH".equals(perm.getPrivilegeName())) { String roleName = perm.getRoleName(); if (roleName.equals(".*")) roleName = "__AWF__ROLE_ALL"; roleName = roleName.replace(' ', '_'); searchData.addSearchAttribute("__AWF__ROLE", roleName, true); } } } } for (ProcessInstanceAttribute attr : processInstance.getProcessAttributes()) { if (attr instanceof Searchable) { Collection<ProcessInstanceSearchAttribute> attributes = ((Searchable) attr).getAttributes(); for (ProcessInstanceSearchAttribute pisa : attributes) { if (pisa.getName().startsWith("__AWF__")) { //no cheating please! String newName = pisa.getName().replace("__AWF__", ""); logger.severe("Renaming process provided attribute " + pisa.getName() + " to " + newName + " as it may clash with internal search attributes. PLEASE CORRECT PROCESS DEFINITION."); pisa.setName(newName); } } searchData.addSearchAttributes(attributes); } } for (String assignee : processInstance.getAssignees()) { searchData.addSearchAttribute("__AWF__assignee", assignee, true); logger.info("__AWF__assignee: "+ assignee); } for (String queue : processInstance.getTaskQueues()) { searchData.addSearchAttribute("__AWF__queue", queue, true); logger.info("__AWF__queue: "+ queue); } searchData.addSearchAttribute("__AWF__running", String.valueOf(processInstance.getRunning()), true); logger.finest("Prepare data for Lucene index update for" + processInstance + " took " + (System.currentTimeMillis()-time) + " ms"); time = System.currentTimeMillis(); searchProvider.updateIndex(searchData); logger.finest("Lucene index update for " + processInstance + " (" + searchData.getSearchAttributes().size() + "attributes) took " + (System.currentTimeMillis()-time) + " ms"); return processInstance.getId(); } public ProcessInstance getProcessInstance(long id) { return (ProcessInstance) session.get(ProcessInstance.class, id); } @Override public List<ProcessInstance> getProcessInstances(Collection<Long> ids) { if (ids.isEmpty()) return new ArrayList(); return getSession().createCriteria(ProcessInstance.class) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) .add(Restrictions.in("id", ids)).list(); } @Override public ProcessInstance refreshProcessInstance(ProcessInstance processInstance) { return (ProcessInstance) getSession().merge(processInstance); } @Override public ProcessInstance getProcessInstanceByInternalId(String internalIds) { long start = System.currentTimeMillis(); ProcessInstance pi = (ProcessInstance) getSession().createCriteria(ProcessInstance.class) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) .add(eq("internalId", internalIds)) .uniqueResult(); long duration = System.currentTimeMillis() - start; logger.severe("getProcessInstanceByInternalId: " + duration); return pi; } @Override public ProcessInstance getProcessInstanceByExternalId(String externalId) { List list = session.createCriteria(ProcessInstance.class) .add(eq("externalKey", externalId)) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) .list(); if (list.isEmpty()) return null; else return (ProcessInstance) list.get(0); } @Override public List<ProcessInstance> findProcessInstancesByKeyword(String keyword, String processType) { return session.createCriteria(ProcessInstance.class) .add(eq("keyword", keyword)) // .add(eq("definition.bpmDefinitionKey", processType)) .addOrder(Order.desc("id")) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) .list(); } @Override public Map<String, ProcessInstance> getProcessInstanceByInternalIdMap(Collection<String> internalId) { if (internalId.isEmpty()) { return new HashMap<String, ProcessInstance>(); } List<ProcessInstance> list = getSession().createCriteria(ProcessInstance.class) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) .add(in("internalId", internalId)) .list(); return Collections.transform(list, new Transformer<ProcessInstance, String>() { @Override public String transform(ProcessInstance obj) { return obj.getInternalId(); } }); } public List<ProcessInstance> getProcessInstancesByIds(List<Long> ids) { return getProcessInstances(ids); } public void deleteProcessInstance(ProcessInstance instance) { long start = System.currentTimeMillis(); session.delete(instance); long duration = System.currentTimeMillis() - start; logger.severe("deleteProcessInstanceByInternalId: " + duration); } public Collection<ProcessInstanceLog> getUserHistory(UserData user, Date startDate, Date endDate) { long start = System.currentTimeMillis(); Criteria criteria = session.createCriteria(ProcessInstanceLog.class) .addOrder(Order.desc("entryDate")); if (user != null) { criteria.add(Restrictions.or(Restrictions.eq("user", user), Restrictions.eq("userSubstitute", user))); } if (startDate != null) { criteria.add(Restrictions.ge("entryDate", asCalendar(truncHours(startDate)))); } if (endDate != null) { criteria.add(Restrictions.le("entryDate", asCalendar(truncHours(addDays(endDate, 1))))); } criteria.createAlias("state", "s", CriteriaSpecification.LEFT_JOIN); criteria.createAlias("processInstance", "pi"); criteria.createAlias("pi.definition", "def"); criteria.createAlias("pi.creator", "crtr"); criteria.createAlias("ownProcessInstance", "ownPi"); criteria.createAlias("user", "u"); criteria.createAlias("userSubstitute", "us", CriteriaSpecification.LEFT_JOIN); ProjectionList pl = Projections.projectionList() .add(Projections.id(), "id") .add(Projections.property("entryDate"), "entryDate") .add(Projections.property("eventI18NKey"), "eventI18NKey") .add(Projections.property("executionId"), "executionId") .add(Projections.property("additionalInfo"), "additionalInfo") .add(Projections.property("u.id"), "user.id") .add(Projections.property("u.login"), "user.login") .add(Projections.property("u.firstName"), "user.firstName") .add(Projections.property("u.lastName"), "user.lastName") .add(Projections.property("s.id"), "state.id") .add(Projections.property("s.name"), "state.name") .add(Projections.property("s.description"), "state.description") .add(Projections.property("us.id"), "userSubstitute.id") .add(Projections.property("us.firstName"), "userSubstitute.firstName") .add(Projections.property("us.lastName"), "userSubstitute.lastName") .add(Projections.property("pi.id"), "processInstance.id") .add(Projections.property("pi.internalId"), "processInstance.internalId") .add(Projections.property("pi.externalKey"), "processInstance.externalKey") .add(Projections.property("pi.status"), "processInstance.status") .add(Projections.property("pi.createDate"), "processInstance.createDate") .add(Projections.property("def.id"), "processInstance.definition.id") .add(Projections.property("def.description"), "processInstance.definition.description") .add(Projections.property("def.comment"), "processInstance.definition.comment") .add(Projections.property("crtr.firstName"), "processInstance.creator.firstName") .add(Projections.property("crtr.lastName"), "processInstance.creator.lastName") .add(Projections.property("ownPi.id"), "ownProcessInstance.id"); criteria.setProjection(pl); criteria.setResultTransformer(new NestedAliasToBeanResultTransformer(ProcessInstanceLog.class)); long duration = System.currentTimeMillis() - start; logger.severe("getUserHistory: " + duration); return criteria.list(); } @Override public Collection<ProcessInstance> getUserProcessesAfterDate(UserData userData, Calendar minDate) { return getUserProcessesBetweenDates(userData,minDate,null); } @Override public Collection<ProcessInstance> getUserProcessesBetweenDates(UserData userData, Calendar minDate, Calendar maxDate) { long start = System.currentTimeMillis(); Session session = getSession(); ProjectionList properties = Projections.projectionList(); properties.add(Projections.property("id")); properties.add(Projections.property("internalId")); Criteria criteria = session.createCriteria(ProcessInstance.class) .setProjection(Projections.distinct(properties)) .createCriteria("processLogs"); if(minDate!=null){ criteria.add(Restrictions.gt("entryDate", minDate)); } if(maxDate!=null){ criteria.add(Restrictions.le("entryDate", maxDate)); } criteria.createAlias("user", "u") .add(Restrictions.eq("u.id", userData.getId())); List<Object[]> list = criteria.list(); Collection<ProcessInstance> collect = Collections.collect(list, new Transformer<Object[], ProcessInstance>() { @Override public ProcessInstance transform(Object[] row) { ProcessInstance pi = new ProcessInstance(); pi.setId((Long) row[0]); pi.setInternalId((String) row[1]); return pi; } }); long duration = System.currentTimeMillis() - start; logger.severe("getUserProcessesBetweenDates: " + duration); return collect; } @Override public ResultsPageWrapper<ProcessInstance> getRecentProcesses(UserData userData, Calendar minDate, Integer offset, Integer limit) { Session session = getSession(); List<ProcessInstance> instances = null; if (offset != null && limit != null) { List<Long> list = session.createCriteria(ProcessInstance.class) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) .setProjection(Projections.distinct(Projections.property("id"))) .addOrder(Order.desc("id")) .setFirstResult(offset) .setMaxResults(limit) .createCriteria("processLogs") .add(Restrictions.gt("entryDate", minDate)) .createAlias("user", "u") .add(Restrictions.eq("u.id", userData.getId())) .list(); instances = getProcessInstancesByIds(list); } Number total = (Number) session.createCriteria(ProcessInstance.class) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) .setProjection(Projections.countDistinct("id")) .createCriteria("processLogs") .add(Restrictions.gt("entryDate", minDate)) .createAlias("user", "u") .add(Restrictions.eq("u.id", userData.getId())).uniqueResult(); ResultsPageWrapper<ProcessInstance> resultsPageWrapper = new ResultsPageWrapper<ProcessInstance>(instances != null ? instances : new ArrayList<ProcessInstance>(), total == null ? 0 : total.intValue()); return resultsPageWrapper; } @Override final public ResultsPageWrapper<ProcessInstance> getProcessInstanceByInternalIdMapWithFilter(final Collection<String> internalIds, final ProcessInstanceFilter filter, Integer offset, Integer limit) { if (internalIds.isEmpty()) { return new ResultsPageWrapper<ProcessInstance>(); } Session session = getSession(); DetachedCriteria detachedCriteriaForIds = buildhibernateQuery(internalIds, session, filter, offset, limit); Criteria criteria = detachedCriteriaForIds.getExecutableCriteria(session); criteria.setFetchMode("definition",FetchMode.SELECT); criteria.setFetchMode("creator",FetchMode.SELECT); criteria.setFetchMode("parent",FetchMode.SELECT); List<ProcessInstance> result = (List<ProcessInstance>)criteria.list(); int resultsCount = result.size(); List<ProcessInstance> list; /* If limit is zero or null, return results */ if(limit == null || limit <= 0) { list = new ArrayList<ProcessInstance>(result); } else { criteria.setFirstResult(offset); criteria.setMaxResults(limit); criteria.addOrder(Order.desc("createDate")); criteria.setProjection(Projections.id());//null).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); List ids = criteria.list(); if (ids != null && !ids.isEmpty()) { DetachedCriteria detachedCriteriaForData = DetachedCriteria.forClass(ProcessInstance.class, "data"); detachedCriteriaForData.addOrder(Order.desc("createDate")); detachedCriteriaForData.add(Property.forName("id").in(ids)); detachedCriteriaForData.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); Criteria criteria2 = detachedCriteriaForData.getExecutableCriteria(session); criteria2.setFetchMode("definition",FetchMode.SELECT); criteria2.setFetchMode("creator",FetchMode.SELECT); criteria2.setFetchMode("parent",FetchMode.SELECT); list = criteria2.list(); } else { list = new ArrayList<ProcessInstance>(0); } } return new ResultsPageWrapper<ProcessInstance>(list, resultsCount); } private DetachedCriteria buildhibernateQuery(Collection<String> internalIds, Session session, ProcessInstanceFilter filter, Integer offset, Integer limit) { DetachedCriteria criteria = DetachedCriteria.forClass(ProcessInstance.class, "ids"); criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); //criteria.setProjection(Projections.distinct(Projections.property("id"))); criteria = criteria.add(Restrictions.in("internalId", internalIds)); if (filter.getCreatedAfter() != null) { criteria = criteria.add(Restrictions.gt("createDate", filter.getCreatedAfter())); } if (filter.getCreatedBefore() != null) { criteria = criteria.add(Restrictions.lt("createDate", filter.getCreatedBefore())); } if (filter.getCreators() != null && !filter.getCreators().isEmpty()) { criteria = criteria.add(Restrictions.in("creator", filter.getCreators())); } if (filter.getUpdatedAfter() != null) { criteria = criteria .createCriteria("processLogs") .add(Restrictions.gt("entryDate", filter.getUpdatedAfterCalendar())); } if (filter.getNotUpdatedAfter() != null) { DetachedCriteria entryDateCriteria = DetachedCriteria.forClass(ProcessInstanceLog.class).add(Restrictions.gt("entryDate", filter.getNotUpdatedAfterCalendar())); criteria = criteria .createCriteria("processLogs") .add(Restrictions.not(Subqueries.exists(entryDateCriteria))); } return criteria; } @Override public Collection<ProcessInstance> searchProcesses(String filter, int offset, int limit, boolean onlyRunning, String[] userRoles, String assignee, String... queues) { long start = System.currentTimeMillis(); List<Long> processIds = searchProvider.searchProcesses(filter, offset, limit, onlyRunning, userRoles, assignee, queues); List<ProcessInstance> processInstancesByIds = getProcessInstancesByIds(processIds); java.util.Collections.sort(processInstancesByIds, new Comparator<ProcessInstance>() { @Override public int compare(ProcessInstance o1, ProcessInstance o2) { return o2.getId().compareTo(o1.getId()); } }); long duration = System.currentTimeMillis() - start; logger.severe("searchProcesses: " + duration); return processInstancesByIds; } }