Java tutorial
/** * ========================================================================================== * = JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION = * ========================================================================================== * * http://www.jahia.com * * Copyright (C) 2002-2017 Jahia Solutions Group SA. All rights reserved. * * THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES: * 1/GPL OR 2/JSEL * * 1/ GPL * ================================================================================== * * IF YOU DECIDE TO CHOOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS: * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * * 2/ JSEL - Commercial and Supported Versions of the program * =================================================================================== * * IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS: * * Alternatively, commercial and supported versions of the program - also known as * Enterprise Distributions - must be used in accordance with the terms and conditions * contained in a separate written agreement between you and Jahia Solutions Group SA. * * If you are unsure which license is appropriate for your use, * please contact the sales department at sales@jahia.com. */ package org.jahia.services.workflow; import org.apache.commons.lang.StringUtils; import org.apache.jackrabbit.core.security.JahiaAccessManager; import org.apache.jackrabbit.core.security.JahiaPrivilegeRegistry; import org.jahia.api.Constants; import org.jahia.data.templates.JahiaTemplatesPackage; import org.jahia.exceptions.JahiaInitializationException; import org.jahia.exceptions.JahiaRuntimeException; import org.jahia.registries.ServicesRegistry; import org.jahia.services.cache.Cache; import org.jahia.services.cache.CacheService; import org.jahia.services.content.*; import org.jahia.services.content.decorator.JCRGroupNode; import org.jahia.services.content.decorator.JCRSiteNode; import org.jahia.services.content.decorator.JCRUserNode; import org.jahia.services.query.QueryWrapper; import org.jahia.services.scheduler.BackgroundJob; import org.jahia.services.templates.JahiaTemplateManagerService; import org.jahia.services.usermanager.JahiaGroupManagerService; import org.jahia.services.usermanager.JahiaPrincipal; import org.jahia.services.usermanager.JahiaUser; import org.jahia.services.usermanager.JahiaUserManagerService; import org.jahia.utils.LanguageCodeConverters; import org.jahia.utils.Patterns; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationListener; import javax.jcr.*; import javax.jcr.query.Query; import javax.jcr.security.Privilege; import java.util.*; /** * Jahia service for managing content workflow. * * @author rincevent * @since JAHIA 6.5 */ public class WorkflowService implements BeanPostProcessor, ApplicationListener<JahiaTemplateManagerService.ModuleDeployedOnSiteEvent> { public static final String CANDIDATE = "candidate"; public static final String START_ROLE = "start"; public static final String WORKFLOWRULES_NODE_NAME = "j:workflowRules"; private static final Logger logger = LoggerFactory.getLogger(WorkflowService.class); private static WorkflowService instance = new WorkflowService();; private Map<String, WorkflowProvider> providers = new HashMap<String, WorkflowProvider>(); private Map<String, WorklowTypeRegistration> workflowRegistrationByDefinition = new HashMap<String, WorklowTypeRegistration>(); private Map<String, String> modulesForWorkflowDefinition = new HashMap<String, String>(); private JCRTemplate jcrTemplate; private WorkflowObservationManager observationManager = new WorkflowObservationManager(this); private CacheService cacheService; private Cache<String, Map<String, WorkflowRule>> cache; private boolean servicesStarted = false; /** * Returns a singleton instance of this service. * * @return a singleton instance of this service */ public static WorkflowService getInstance() { return instance; } public void setCacheService(CacheService cacheService) { this.cacheService = cacheService; } public void start() throws JahiaInitializationException { if (cacheService != null) { cache = cacheService.getCache("WorkflowRuleCache", true); } } /** * Performs the registration of the provided workflow type. * * @param type the helper object instance for registerng a new workflow type */ public synchronized void registerWorkflowType(final WorklowTypeRegistration type) { if (type != null && !workflowRegistrationByDefinition.containsKey(type.getDefinition())) { workflowRegistrationByDefinition.put(type.getDefinition(), type); // During startup, when things are still being registered by multiple threads, registration cannot be // completed reliably. So, avoid finishing it and rely on registerWorkflowTypes invoked during the post- // initialization phase instead. if (servicesStarted) { doRegisterWorkflowType(type); } } } private void doRegisterWorkflowType(final WorklowTypeRegistration type) { if (type.getModule() != null) { modulesForWorkflowDefinition.put(type.getDefinition(), type.getModule().getId()); for (WorkflowProvider provider : providers.values()) { final WorkflowDefinition def = provider.getWorkflowDefinitionByKey(type.getDefinition(), null); if (def != null) { try { JCRTemplate.getInstance().doExecuteWithSystemSession(new JCRCallback<Object>() { @Override public Object doInJCR(JCRSessionWrapper session) throws RepositoryException { boolean updated = initializePermission(session, def, type.getModule()); if (updated) { session.save(); JahiaPrivilegeRegistry.addModulePrivileges(session, "/modules/" + type.getModule().getIdWithVersion()); } return null; } }); } catch (RepositoryException e) { logger.error("Cannot register workflow permissions", e); } type.setProvider(provider.getKey()); cache.flush(); ServicesRegistry.getInstance().getJahiaTemplateManagerService().getTemplatePackageRegistry() .addPackageForResourceBundle(def.getPackageName() + "." + type.getDefinition(), type.getModule()); break; } } } if (type.getProvider() == null) { workflowRegistrationByDefinition.remove(type.getDefinition()); modulesForWorkflowDefinition.remove(type.getDefinition()); } } public synchronized void registerWorkflowTypes() { for (WorklowTypeRegistration registration : new LinkedList<WorklowTypeRegistration>( workflowRegistrationByDefinition.values())) { doRegisterWorkflowType(registration); } } /** * Performs the unregistration of the provided workflow type. * * @param type the helper object instance for unregisterng a workflow type */ public synchronized void unregisterWorkflowType(WorklowTypeRegistration type) { if (workflowRegistrationByDefinition.get(type.getDefinition()) == type) { workflowRegistrationByDefinition.remove(type.getDefinition()); modulesForWorkflowDefinition.remove(type.getDefinition()); cache.flush(); } } /** * Returns a map with the registered workflow providers. * * @return a map with the registered workflow providers */ public Map<String, WorkflowProvider> getProviders() { return providers; } /** * Adds the specified workflow provider into the registry. * * @param provider a workflow provider to be registered */ public synchronized void addProvider(final WorkflowProvider provider) { providers.put(provider.getKey(), provider); if (provider instanceof WorkflowObservationManagerAware) { ((WorkflowObservationManagerAware) provider).setWorkflowObservationManager(observationManager); } } /** * Removes the specified provider from the registry. * * @param provider the provider to be removed */ public synchronized void removeProvider(final WorkflowProvider provider) { providers.remove(provider.getKey()); } private synchronized boolean initializePermission(JCRSessionWrapper session, WorkflowDefinition definition, JahiaTemplatesPackage module) throws RepositoryException { boolean updated = false; Map<String, String> map = workflowRegistrationByDefinition.get(definition.getKey()).getPermissions(); if (map == null) { map = new HashMap<String, String>(); workflowRegistrationByDefinition.get(definition.getKey()).setPermissions(map); } Set<String> tasks = definition.getTasks(); final String permissionPath = "/modules/" + module.getIdWithVersion() + "/permissions"; for (String task : tasks) { if (!map.containsKey(task)) { String permissionName = Patterns.SPACE.matcher(definition.getKey()).replaceAll("-") + "-" + Patterns.SPACE.matcher(task).replaceAll("-"); if (!session.itemExists(permissionPath + "/workflow-tasks/" + permissionName)) { logger.info("Create workflow permission : " + permissionName); JCRNodeWrapper perms = session.getNode(permissionPath); if (!perms.hasNode("workflow-tasks")) { perms.addNode("workflow-tasks", "jnt:permission"); } perms.getNode("workflow-tasks").addNode(permissionName, "jnt:permission"); updated = true; } map.put(task, "/workflow-tasks/" + permissionName); } } return updated; } /** * This method list all workflows deployed in the system * * @param displayLocale the UI display locale * @return A list of available workflows per provider. * @throws RepositoryException in case of an error */ public List<WorkflowDefinition> getWorkflows(Locale displayLocale) throws RepositoryException { List<WorkflowDefinition> workflowsByProvider = new ArrayList<WorkflowDefinition>(); for (Map.Entry<String, WorkflowProvider> providerEntry : providers.entrySet()) { workflowsByProvider.addAll(providerEntry.getValue().getAvailableWorkflows(displayLocale)); } return workflowsByProvider; } /** * Returns a list of available workflow definitions for the specified type. * * @param type workflow type * @param uiLocale the locale used to localize workflow labels @return a list of available workflow definitions for the specified type * @throws RepositoryException in case of an error */ public List<WorkflowDefinition> getWorkflowDefinitionsForType(String type, Locale uiLocale) throws RepositoryException { return getWorkflowDefinitionsForType(type, null, uiLocale); } /** * Returns a list of available workflow definitions for the specified type. * * @param type workflow type * @param siteNode site node * @param uiLocale the locale used to localize workflow labels @return a list of available workflow definitions for the specified type * @throws RepositoryException in case of an error */ public List<WorkflowDefinition> getWorkflowDefinitionsForType(String type, JCRSiteNode siteNode, Locale uiLocale) throws RepositoryException { List<WorkflowDefinition> workflowsByProvider = new ArrayList<WorkflowDefinition>(); for (Map.Entry<String, WorkflowProvider> providerEntry : providers.entrySet()) { List<WorkflowDefinition> defs = providerEntry.getValue().getAvailableWorkflows(uiLocale); for (WorkflowDefinition def : defs) { WorklowTypeRegistration worklowTypeRegistration = workflowRegistrationByDefinition .get(def.getKey()); if (worklowTypeRegistration.getType().equals(type) && (siteNode == null || isRegistrationAvailableForSite(siteNode, worklowTypeRegistration))) { workflowsByProvider.add(def); } } } return workflowsByProvider; } private boolean isRegistrationAvailableForSite(JCRSiteNode siteNode, WorklowTypeRegistration worklowTypeRegistration) { return worklowTypeRegistration.getModule().getModuleType().equals("system") || siteNode .getInstalledModulesWithAllDependencies().contains(worklowTypeRegistration.getModule().getId()); } /** * This method list all possible workflows for the specified node. * * @param node * @param checkPermission * @return A list of available workflows per provider. */ public Map<String, WorkflowDefinition> getPossibleWorkflows(final JCRNodeWrapper node, boolean checkPermission, Locale uiLocale) throws RepositoryException { List<WorkflowDefinition> l = getPossibleWorkflows(node, checkPermission, null, uiLocale); Map<String, WorkflowDefinition> res = new HashMap<String, WorkflowDefinition>(); for (WorkflowDefinition workflowDefinition : l) { res.put(workflowRegistrationByDefinition.get(workflowDefinition.getKey()).getType(), workflowDefinition); } return res; } /** * This method return the workflow associated to an type, for the specified node. * * @param node * @param checkPermission * @return A list of available workflows per provider. */ public WorkflowDefinition getPossibleWorkflowForType(final JCRNodeWrapper node, final boolean checkPermission, final String type, final Locale locale) throws RepositoryException { final List<WorkflowDefinition> workflowDefinitionList = getPossibleWorkflows(node, checkPermission, type, locale); if (workflowDefinitionList.isEmpty()) { return null; } return workflowDefinitionList.get(0); } /** * This method list all possible workflows for the specified node. * * @param node * @param checkPermission * @return A list of available workflows per provider. */ private List<WorkflowDefinition> getPossibleWorkflows(final JCRNodeWrapper node, final boolean checkPermission, final String type, final Locale uiLocale) throws RepositoryException { final Set<WorkflowDefinition> workflows = new LinkedHashSet<WorkflowDefinition>(); Collection<WorkflowRule> rules = getWorkflowRulesForType(node, checkPermission, type); for (WorkflowRule ruledef : rules) { WorkflowDefinition definition = lookupProvider(ruledef.getProviderKey()) .getWorkflowDefinitionByKey(ruledef.getWorkflowDefinitionKey(), uiLocale); if (definition != null) { workflows.add(definition); } } return new LinkedList<WorkflowDefinition>(workflows); } public List<JahiaPrincipal> getAssignedRole(final WorkflowDefinition definition, final String activityName, final String processId) throws RepositoryException { return jcrTemplate.doExecuteWithSystemSession(new JCRCallback<List<JahiaPrincipal>>() { @Override public List<JahiaPrincipal> doInJCR(JCRSessionWrapper session) throws RepositoryException { return getAssignedRole(definition, activityName, processId, session); } }); } public List<JahiaPrincipal> getAssignedRole(WorkflowDefinition definition, String activityName, String processId, JCRSessionWrapper session) throws RepositoryException { List<JahiaPrincipal> principals = Collections.emptyList(); Map<String, String> perms = workflowRegistrationByDefinition.get(definition.getKey()).getPermissions(); String permPath = perms != null ? perms.get(activityName) : null; if (permPath == null) { return principals; } Workflow w = getWorkflow(definition.getProvider(), processId, null); JCRNodeWrapper node = session.getNodeByIdentifier((String) w.getVariables().get("nodeId")); if (permPath.indexOf("$") > -1) { if (w != null) { for (Map.Entry<String, Object> entry : w.getVariables().entrySet()) { Object value = entry.getValue(); if (value instanceof List) { List<?> list = (List<?>) entry.getValue(); StringBuilder sb = new StringBuilder(); Iterator<?> iterator = list.iterator(); while (iterator.hasNext()) { Object o = iterator.next(); if (o instanceof WorkflowVariable) { sb.append(((WorkflowVariable) o).getValue()); } if (iterator.hasNext()) { sb.append(","); } } permPath = permPath.replace("$" + entry.getKey(), iterator.toString()); } else if (value instanceof WorkflowVariable) { permPath = permPath.replace("$" + entry.getKey(), ((WorkflowVariable) value).getValue()); } } } } try { if (!permPath.contains("/")) { Query q = session.getWorkspace().getQueryManager().createQuery( "select * from [jnt:permission] where name()='" + JCRContentUtils.sqlEncode(permPath) + "'", Query.JCR_SQL2); NodeIterator ni = q.execute().getNodes(); if (ni.hasNext()) { permPath = StringUtils.substringAfter(ni.nextNode().getPath(), "/permissions"); } else { return principals; } } Set<String> roles = new HashSet<String>(); Set<String> extPerms = new HashSet<String>(); while (!StringUtils.isEmpty(permPath)) { String permissionName = permPath.contains("/") ? StringUtils.substringAfterLast(permPath, "/") : permPath; NodeIterator ni = session.getWorkspace().getQueryManager() .createQuery("select * from [jnt:role] where [j:permissionNames] = '" + JCRContentUtils.sqlEncode(permissionName) + "'", Query.JCR_SQL2) .execute().getNodes(); while (ni.hasNext()) { JCRNodeWrapper roleNode = (JCRNodeWrapper) ni.next(); roles.add(roleNode.getName()); } ni = session.getWorkspace().getQueryManager() .createQuery("select * from [jnt:externalPermissions] where [j:permissionNames] = '" + JCRContentUtils.sqlEncode(permissionName) + "'", Query.JCR_SQL2) .execute().getNodes(); while (ni.hasNext()) { JCRNodeWrapper roleNode = (JCRNodeWrapper) ni.next(); extPerms.add(roleNode.getParent().getName() + "/" + roleNode.getName()); } permPath = permPath.contains("/") ? StringUtils.substringBeforeLast(permPath, "/") : ""; } Map<String, List<String[]>> m = node.getAclEntries(); principals = new LinkedList<JahiaPrincipal>(); JahiaUserManagerService userService = ServicesRegistry.getInstance().getJahiaUserManagerService(); JahiaGroupManagerService groupService = ServicesRegistry.getInstance().getJahiaGroupManagerService(); JCRSiteNode site = null; for (Map.Entry<String, List<String[]>> entry : m.entrySet()) { for (String[] strings : entry.getValue()) { if (strings[1].equals("GRANT") && roles.contains(strings[2]) || strings[1].equals("EXTERNAL") && extPerms.contains(strings[2])) { String principal = entry.getKey(); final String principalName = principal.substring(2); if (site == null) { site = node.getResolveSite(); } if (principal.charAt(0) == 'u') { JCRUserNode userNode = userService.lookupUser(principalName, strings[0].startsWith("/sites/") ? site.getSiteKey() : null); if (userNode != null) { if (logger.isDebugEnabled()) { logger.debug("user " + userNode.getUserKey() + " is granted"); } JahiaUser jahiaUser = userNode.getJahiaUser(); principals.add(jahiaUser); } } else if (principal.charAt(0) == 'g') { JCRGroupNode group = groupService.lookupGroup(site.getSiteKey(), principalName); if (group == null) { group = groupService.lookupGroup(null, principalName); } if (group != null) { if (logger.isDebugEnabled()) { logger.debug("group " + group.getGroupKey() + " is granted"); } principals.add(group.getJahiaGroup()); } } } } } } catch (RepositoryException e) { logger.error(e.getMessage(), e); } catch (BeansException e) { logger.error(e.getMessage(), e); } return principals; } /** * This method list all currently active workflow for the specified node. * * @param node * @param locale the content locale * @param displayLocale the UI display locale * @return A list of active workflows per provider */ public List<Workflow> getActiveWorkflows(JCRNodeWrapper node, Locale locale, Locale displayLocale) { List<Workflow> workflows = new ArrayList<Workflow>(); try { Node n = node; if (n.isNodeType(Constants.JAHIAMIX_WORKFLOW) && n.hasProperty(Constants.PROCESSID)) { addActiveWorkflows(workflows, n.getProperty(Constants.PROCESSID), displayLocale); } try { if (locale != null && node.hasTranslations()) { n = node.getI18N(locale); if (n.isNodeType(Constants.JAHIAMIX_WORKFLOW) && n.hasProperty(Constants.PROCESSID)) { addActiveWorkflows(workflows, n.getProperty(Constants.PROCESSID), displayLocale); } } } catch (ItemNotFoundException e) { return workflows; } } catch (RepositoryException e) { logger.error(e.getMessage(), e); } return workflows; } /** * This method list all currently active workflow for the specified node. * * @param node * @return A list of active workflows per provider */ public Map<Locale, List<Workflow>> getActiveWorkflowsForAllLocales(JCRNodeWrapper node) { Map<Locale, List<Workflow>> workflowsByLocale = new HashMap<Locale, List<Workflow>>(); try { if (node.isNodeType(Constants.JAHIAMIX_WORKFLOW)) { NodeIterator ni = node.getNodes("j:translation*"); while (ni.hasNext()) { Node n = ((JCRNodeWrapper) ni.next()).getRealNode(); final String lang = n.getProperty("jcr:language").getString(); if (n.hasProperty(Constants.PROCESSID)) { List<Workflow> l = new ArrayList<Workflow>(); workflowsByLocale.put(LanguageCodeConverters.getLocaleFromCode(lang), l); addActiveWorkflows(l, n.getProperty(Constants.PROCESSID), null); } } } } catch (RepositoryException e) { logger.error(e.getMessage(), e); } return workflowsByLocale; } private void addActiveWorkflows(List<Workflow> workflows, Property p, Locale displayLocale) throws RepositoryException { Value[] values = p.getValues(); for (Map.Entry<String, WorkflowProvider> entry : providers.entrySet()) { final List<String> processIds = new ArrayList<String>(values.length); for (Value value : values) { String key = value.getString(); String processId = StringUtils.substringAfter(key, ":"); String providerKey = StringUtils.substringBefore(key, ":"); if (providerKey.equals(entry.getKey())) { processIds.add(processId); } } if (!processIds.isEmpty()) { List<Workflow> workflowsInformations = entry.getValue().getActiveWorkflowsInformations(processIds, displayLocale); workflows.addAll(workflowsInformations); } } } /** * This method list all actions available at execution time for a node. * * @param processId the process we want to advance * @param provider The provider executing the process * @param locale * @return a set of actions per workflows per provider. */ public Set<WorkflowAction> getAvailableActions(String processId, String provider, Locale locale) { return lookupProvider(provider).getAvailableActions(processId, locale); } /** * This method will call the underlying provider to signal the identified process. * * @param processId the process we want to advance * @param provider The provider executing the process */ public void abortProcess(String processId, String provider) { Workflow workflow = lookupProvider(provider).getWorkflow(processId, null); final Set<String> actionIds = new HashSet<String>(); for (final WorkflowAction action : workflow.getAvailableActions()) { if (action instanceof WorkflowTask) { actionIds.add(((WorkflowTask) action).getId()); } } if (!actionIds.isEmpty()) { try { JCRTemplate.getInstance().doExecuteWithSystemSession(new JCRCallback<Object>() { @Override public Object doInJCR(JCRSessionWrapper session) throws RepositoryException { for (String actionId : actionIds) { QueryWrapper q = session.getWorkspace().getQueryManager().createQuery( "select * from [jnt:workflowTask] where [taskId]='" + actionId + "'", Query.JCR_SQL2); JCRNodeIteratorWrapper ni = q.execute().getNodes(); for (JCRNodeWrapper wrapper : ni) { wrapper.remove(); } } session.save(); return false; } }); } catch (RepositoryException e) { logger.error("Cannot remove tasks", e); } } lookupProvider(provider).abortProcess(processId); } public void startProcessAsJob(List<String> nodeIds, JCRSessionWrapper session, String processKey, String provider, Map<String, Object> args, List<String> comments) throws RepositoryException, SchedulerException { JobDetail jobDetail = BackgroundJob.createJahiaJob("StartProcess", StartProcessJob.class); JobDataMap jobDataMap = jobDetail.getJobDataMap(); jobDataMap.put(BackgroundJob.JOB_USERKEY, session.getUserNode().getUserKey()); jobDataMap.put(BackgroundJob.JOB_CURRENT_LOCALE, session.getLocale().toString()); jobDataMap.put(StartProcessJob.NODE_IDS, nodeIds); jobDataMap.put(StartProcessJob.PROVIDER, provider); jobDataMap.put(StartProcessJob.PROCESS_KEY, processKey); jobDataMap.put(StartProcessJob.MAP, args); jobDataMap.put(StartProcessJob.COMMENTS, comments); ServicesRegistry.getInstance().getSchedulerService().scheduleJobNow(jobDetail); } public String startProcess(List<String> nodeIds, JCRSessionWrapper session, String processKey, String provider, Map<String, Object> args, List<String> comments) throws RepositoryException { boolean debugEnabled = logger.isDebugEnabled(); long startTime = debugEnabled ? System.currentTimeMillis() : 0; // retrieve the permission, required to start the workflow String startPermission = getPermissionForStart(workflowRegistrationByDefinition.get(processKey)); WorkflowProvider providerImpl = lookupProvider(provider); List<String> checkedNodeIds = new ArrayList<String>(); for (String nodeId : nodeIds) { try { JCRNodeWrapper n = session.getNodeByIdentifier(nodeId); if (startPermission == null || n.hasPermission(startPermission)) { checkedNodeIds.add(nodeId); } } catch (ItemNotFoundException e) { // Item does not exist } } if (checkedNodeIds.isEmpty()) { return null; } String mainId = checkedNodeIds.iterator().next(); Map<String, Object> newArgs = new HashMap<String, Object>(); for (Map.Entry<String, Object> entry : args.entrySet()) { newArgs.put(entry.getKey().replaceAll(":", "_"), entry.getValue()); } newArgs.put("nodeId", mainId); try { newArgs.put("nodePath", session.getNodeByIdentifier(mainId).getPath()); } catch (ItemNotFoundException e) { // Node not found } newArgs.put("nodeIds", checkedNodeIds); newArgs.put("workspace", session.getWorkspace().getName()); newArgs.put("locale", session.getLocale()); newArgs.put("workflow", providerImpl.getWorkflowDefinitionByKey(processKey, session.getLocale())); newArgs.put("user", session.getUser() != null ? session.getUser().getUserKey() : null); if (comments != null && comments.size() > 0) { addCommentsToVariables(newArgs, comments, session.getUser().getUserKey()); } final String processId = providerImpl.startProcess(processKey, newArgs); if (debugEnabled) { // if trace is enabled we log also the UUIDs of all nodes logger.debug( "A workflow {} from {} has been started on {}{} nodes{}" + " from workspace {} in locale {} with id {}{} in {} ms", new Object[] { processKey, provider, checkedNodeIds.size(), nodeIds.size() > checkedNodeIds.size() ? " (originally " + nodeIds.size() + ")" : "", logger.isTraceEnabled() ? ": " + checkedNodeIds : "", newArgs.get("workspace"), newArgs.get("locale"), processId, startPermission != null ? " checking for permission " + startPermission : "", System.currentTimeMillis() - startTime }); } return processId; } private void addCommentsToVariables(Map<String, Object> args, List<String> comments, String userKey) { @SuppressWarnings("unchecked") List<WorkflowComment> wfComments = (List<WorkflowComment>) args.get("comments"); if (wfComments == null) { wfComments = new LinkedList<WorkflowComment>(); args.put("comments", wfComments); } Date timestamp = new Date(); for (String comment : comments) { wfComments.add(new WorkflowComment(comment, timestamp, userKey)); } } public synchronized void addProcessId(JCRNodeWrapper stageNode, String provider, String processId) throws RepositoryException { stageNode.checkout(); if (!stageNode.isNodeType(Constants.JAHIAMIX_WORKFLOW)) { stageNode.addMixin(Constants.JAHIAMIX_WORKFLOW); } List<Value> values; if (stageNode.hasProperty(Constants.PROCESSID)) { values = new ArrayList<Value>(Arrays.asList(stageNode.getProperty(Constants.PROCESSID).getValues())); } else { values = new ArrayList<Value>(); } values.add(stageNode.getSession().getValueFactory().createValue(provider + ":" + processId)); stageNode.setProperty(Constants.PROCESSID, values.toArray(new Value[values.size()])); stageNode.getSession().save(); } public synchronized void removeProcessId(JCRNodeWrapper stageNode, String provider, String processId) throws RepositoryException { if (!stageNode.hasProperty(Constants.PROCESSID)) { return; } stageNode.checkout(); List<Value> values = new ArrayList<Value>( Arrays.asList(stageNode.getProperty(Constants.PROCESSID).getValues())); List<Value> newValues = new ArrayList<Value>(); for (Value value : values) { if (!value.getString().equals(provider + ":" + processId)) { newValues.add(value); } } if (newValues.isEmpty()) { if (stageNode.hasProperty(Constants.PROCESSID)) { stageNode.getProperty(Constants.PROCESSID).remove(); } } else { stageNode.setProperty(Constants.PROCESSID, newValues.toArray(new Value[newValues.size()])); } stageNode.getSession().save(); } public List<WorkflowTask> getTasksForUser(JahiaUser user, Locale uiLocale) { final List<WorkflowTask> workflowActions = new LinkedList<WorkflowTask>(); for (Map.Entry<String, WorkflowProvider> providerEntry : providers.entrySet()) { workflowActions.addAll(providerEntry.getValue().getTasksForUser(user, uiLocale)); } return workflowActions; } public List<Workflow> getWorkflowsForUser(JahiaUser user, Locale uiLocale) { final List<Workflow> workflow = new LinkedList<Workflow>(); for (Map.Entry<String, WorkflowProvider> providerEntry : providers.entrySet()) { workflow.addAll(providerEntry.getValue().getWorkflowsForUser(user, uiLocale)); } return workflow; } public List<Workflow> getWorkflowsForType(String type, Locale uiLocale) { List<Workflow> list = new ArrayList<Workflow>(); for (WorklowTypeRegistration registration : workflowRegistrationByDefinition.values()) { if (registration.getType().equals(type)) { list.addAll(getWorkflowsForDefinition(registration.getDefinition(), uiLocale)); } } return list; } public List<Workflow> getWorkflowsForDefinition(String definition, Locale uiLocale) { List<Workflow> list = new ArrayList<Workflow>(); for (WorkflowProvider provider : providers.values()) { list.addAll(provider.getWorkflowsForDefinition(definition, uiLocale)); } return list; } public void assignTask(String taskId, String provider, JahiaUser user) { if (logger.isDebugEnabled()) { logger.debug("Assigning user " + user + " to task " + taskId); } lookupProvider(provider).assignTask(taskId, user); } public void completeTask(String taskId, JahiaUser user, String provider, String outcome, Map<String, Object> args) { lookupProvider(provider).completeTask(taskId, user, outcome, args); } public void assignAndCompleteTaskAsJob(String taskId, String provider, String outcome, Map<String, Object> args, JahiaUser user) throws RepositoryException, SchedulerException { JobDetail jobDetail = BackgroundJob.createJahiaJob("AssignAndCompleteTask", AssignAndCompleteTaskJob.class); JobDataMap jobDataMap = jobDetail.getJobDataMap(); jobDataMap.put(BackgroundJob.JOB_USERKEY, user.getUserKey()); jobDataMap.put(AssignAndCompleteTaskJob.TASK_ID, taskId); jobDataMap.put(AssignAndCompleteTaskJob.PROVIDER, provider); jobDataMap.put(AssignAndCompleteTaskJob.OUTCOME, outcome); jobDataMap.put(AssignAndCompleteTaskJob.MAP, args); ServicesRegistry.getInstance().getSchedulerService().scheduleJobNow(jobDetail); } public void assignAndCompleteTask(String taskId, String provider, String outcome, Map<String, Object> args, JahiaUser user) { assignTask(taskId, provider, user); completeTask(taskId, user, provider, outcome, args); } public void addWorkflowRule(final JCRNodeWrapper node, final WorkflowDefinition workflow) throws RepositoryException { // store the rule JCRNodeWrapper rules = null; try { rules = node.getNode(WORKFLOWRULES_NODE_NAME); } catch (RepositoryException e) { if (!node.isCheckedOut()) { node.checkout(); } node.addMixin("jmix:workflowRulesable"); rules = node.addNode(WORKFLOWRULES_NODE_NAME, "jnt:workflowRules"); } JCRNodeWrapper n; String wfName = workflow.getProvider() + "_" + workflow.getKey(); if (rules.hasNode(wfName)) { n = rules.getNode(wfName); } else { n = rules.addNode(wfName, "jnt:workflowRule"); } if (!n.isCheckedOut()) { n.checkout(); } n.setProperty("j:workflow", workflow.getProvider() + ":" + workflow.getKey()); } public void addComment(String processId, String provider, String comment, String user) { lookupProvider(provider).addComment(processId, comment, user); } public WorkflowTask getWorkflowTask(String taskId, String provider, Locale displayLocale) { WorkflowTask workflowTask = lookupProvider(provider).getWorkflowTask(taskId, displayLocale); return workflowTask; } public HistoryWorkflow getHistoryWorkflow(String id, String provider, Locale uiLocale) { List<HistoryWorkflow> list = providers.get(provider).getHistoryWorkflows(Collections.singletonList(id), uiLocale); if (!list.isEmpty()) { return list.get(0); } else { return null; } } /** * Returns a list of process instance history records for the specified * node. This method also returns "active" (i.e. not completed) workflow * process instance. * * @param node the JCR node to retrieve history records for * @param uiLocale the current UI locale * @return a list of process instance history records for the specified node */ public List<HistoryWorkflow> getHistoryWorkflows(JCRNodeWrapper node, Locale uiLocale) { List<HistoryWorkflow> history = new LinkedList<HistoryWorkflow>(); try { for (WorkflowProvider workflowProvider : providers.values()) { history.addAll(workflowProvider.getHistoryWorkflowsForNode(node.getIdentifier(), uiLocale)); } } catch (RepositoryException e) { logger.error(e.getMessage(), e); } return history; } /** * Returns a list of process instance history records for the specified * path. This method also returns "active" (i.e. not completed) workflow * process instance. * * @param path the Path of the node to retrieve history records for * @param locale * @return a list of process instance history records for the specified node */ public List<HistoryWorkflow> getHistoryWorkflowsByPath(String path, Locale locale) { List<HistoryWorkflow> history = new LinkedList<HistoryWorkflow>(); for (WorkflowProvider workflowProvider : providers.values()) { history.addAll(workflowProvider.getHistoryWorkflowsForPath(path, locale)); } return history; } /** * Returns a list of history records for workflow tasks. * This method also returns not completed tasks. * * @param workflowProcessId the process instance ID * @param providerKey the workflow provider key * @param uiLocale current UI display locale * @return a list of history records for workflow tasks */ public List<HistoryWorkflowTask> getHistoryWorkflowTasks(String workflowProcessId, String providerKey, Locale uiLocale) { List<HistoryWorkflowTask> list = lookupProvider(providerKey).getHistoryWorkflowTasks(workflowProcessId, uiLocale); return list; } protected WorkflowProvider lookupProvider(String key) { WorkflowProvider provider = providers.get(key); if (provider == null) { throw new JahiaRuntimeException("Unknown workflow provider with the key '" + key + "'"); } return provider; } /** * This method list all currently active workflow for the specified node. * * @param node * @param type * @return A list of active workflows per provider */ public boolean hasActiveWorkflowForType(JCRNodeWrapper node, String type) { List<Workflow> workflows = new ArrayList<Workflow>(); try { final List<WorkflowDefinition> forAction = getWorkflowDefinitionsForType(type, null); if (node.isNodeType(Constants.JAHIAMIX_WORKFLOW) && node.hasProperty(Constants.PROCESSID)) { addActiveWorkflows(workflows, node.getProperty(Constants.PROCESSID), node.getSession().getLocale()); } for (Workflow workflow : workflows) { if (forAction.contains(workflow.getWorkflowDefinition())) { return true; } } } catch (RepositoryException e) { logger.error(e.getMessage(), e); } return false; } public void addWorkflowRule(JCRNodeWrapper node, String wfName) throws RepositoryException { String provider = StringUtils.substringBefore(wfName, ":"); String wfKey = StringUtils.substringAfter(wfName, ":"); WorkflowDefinition definition = providers.get(provider).getWorkflowDefinitionByKey(wfKey, node.getSession().getLocale()); addWorkflowRule(node, definition); } public WorkflowRule getWorkflowRuleForAction(JCRNodeWrapper objectNode, boolean checkPermission, String action) throws RepositoryException { Collection<WorkflowRule> rules = getWorkflowRulesForType(objectNode, checkPermission, action); if (rules.isEmpty()) { return null; } else { return rules.iterator().next(); } } private Collection<WorkflowRule> getWorkflowRulesForType(JCRNodeWrapper objectNode, boolean checkPermission, String type) throws RepositoryException { Collection<WorkflowRule> results = new LinkedHashSet<WorkflowRule>(); Collection<WorkflowRule> rules = getWorkflowRules(objectNode); for (WorkflowRule rule : rules) { final WorklowTypeRegistration worklowTypeRegistration = workflowRegistrationByDefinition .get(rule.getWorkflowDefinitionKey()); if (type == null || worklowTypeRegistration.getType().equals(type)) { String permName = checkPermission ? getPermissionForStart(worklowTypeRegistration) : null; if (permName == null || objectNode.hasPermission(permName)) { results.add(rule); } } } return results; } public Collection<WorkflowRule> getWorkflowRules(JCRNodeWrapper objectNode) { try { Map<String, WorkflowRule> rules = recurseOnRules(objectNode); Map<String, List<String>> perms = new HashMap<>(); JCRNodeWrapper rootNode = objectNode.getSession().getNode("/"); JahiaAccessManager accessControlManager = (JahiaAccessManager) rootNode.getRealNode().getSession() .getAccessControlManager(); if (objectNode.getAclEntries() != null) { for (List<String[]> list : objectNode.getAclEntries().values()) { for (String[] strings : list) { for (Privilege privilege : accessControlManager.getPermissionsInRole(strings[2])) { if (!perms.containsKey(strings[0])) { perms.put(strings[0], new ArrayList<String>()); } perms.get(strings[0]).add(JCRContentUtils.getJCRName(privilege.getName(), objectNode.getRealNode().getSession().getWorkspace().getNamespaceRegistry())); } } } } Map<String, WorkflowRule> rulesCopy = new HashMap<>(rules); for (Map.Entry<String, WorkflowRule> ruleEntry : rules.entrySet()) { WorkflowRule rule = ruleEntry.getValue(); for (Map.Entry<String, List<String>> aclEntry : perms.entrySet()) { if (aclEntry.getKey().startsWith( rule.getDefinitionPath().equals("/") ? "/" : rule.getDefinitionPath() + "/")) { if (!Collections.disjoint(aclEntry.getValue(), rule.getPermissions().values())) { rule = new WorkflowRule(aclEntry.getKey(), ruleEntry.getValue().getDefinitionPath(), rule.getProviderKey(), rule.getWorkflowDefinitionKey(), rule.getPermissions()); rulesCopy.put(ruleEntry.getKey(), rule); } } } } return Collections.unmodifiableCollection(rulesCopy.values()); } catch (RepositoryException e) { logger.error(e.getMessage(), e); } return null; } private Map<String, WorkflowRule> recurseOnRules(final JCRNodeWrapper n) throws RepositoryException { String nodePath = n.getPath(); Map<String, WorkflowRule> results = cache.get(nodePath); if (results != null) { return results; } if ("/".equals(nodePath)) { results = getDefaultRules(n); } else { if (n.isNodeType("jnt:virtualsite")) { results = getDefaultRules(n); } else { results = recurseOnRules(n.getParent()); } if (n.hasNode(WORKFLOWRULES_NODE_NAME)) { results = new HashMap<String, WorkflowRule>(results); Node wfRules = n.getNode(WORKFLOWRULES_NODE_NAME); NodeIterator rules = wfRules.getNodes(); while (rules.hasNext()) { Node rule = rules.nextNode(); final String wfName = rule.getProperty("j:workflow").getString(); String name = StringUtils.substringAfter(wfName, ":"); String prov = StringUtils.substringBefore(wfName, ":"); final WorklowTypeRegistration type = workflowRegistrationByDefinition.get(name); if (type == null) { continue; } String wftype = type.getType(); results.put(wftype, new WorkflowRule(nodePath, nodePath, prov, name, type.getPermissions())); } } } cache.put(nodePath, results); return results; } @Override public void onApplicationEvent(JahiaTemplateManagerService.ModuleDeployedOnSiteEvent event) { cache.flush(); } private Map<String, WorkflowRule> getDefaultRules(JCRNodeWrapper n) throws RepositoryException { Map<String, WorkflowRule> results = new HashMap<String, WorkflowRule>(); Map<String, WorklowTypeRegistration> m = new HashMap<String, WorklowTypeRegistration>(); for (WorklowTypeRegistration registration : workflowRegistrationByDefinition.values()) { if (registration.isCanBeUsedForDefault() && (!m.containsKey(registration.getType()) || m.get(registration.getType()).getDefaultPriority() < registration.getDefaultPriority()) && isRegistrationAvailableForSite(n.getResolveSite(), registration)) { m.put(registration.getType(), registration); } } for (Map.Entry<String, WorklowTypeRegistration> entry : m.entrySet()) { results.put(entry.getValue().getType(), new WorkflowRule("/", "/", entry.getValue().getProvider(), entry.getValue().getDefinition(), entry.getValue().getPermissions())); } return results; } public Workflow getWorkflow(String provider, String id, Locale displayLocale) { return lookupProvider(provider).getWorkflow(id, displayLocale); } public WorkflowDefinition getWorkflowDefinition(String provider, String id, Locale locale) { if (getWorkflowRegistration(id) == null) { return null; } final WorkflowDefinition definition = lookupProvider(provider).getWorkflowDefinitionByKey(id, locale); return definition; } public WorklowTypeRegistration getWorkflowRegistration(String definitionKey) { return workflowRegistrationByDefinition.get(definitionKey); } public String getWorkflowType(WorkflowDefinition def) { return workflowRegistrationByDefinition.get(def.getKey()).getType(); } public String getFormForAction(String definitionKey, String action) { if (workflowRegistrationByDefinition.get(definitionKey).getForms() != null) { return workflowRegistrationByDefinition.get(definitionKey).getForms().get(action); } return null; } public String getModuleForWorkflow(String key) { return modulesForWorkflowDefinition.get(key); } public Set<String> getTypesOfWorkflow() { Set<String> s = new HashSet<String>(); for (WorklowTypeRegistration registration : workflowRegistrationByDefinition.values()) { s.add(registration.getType()); } return s; } public void deleteProcess(String processId, String provider) { providers.get(provider).deleteProcess(processId); } public void addWorkflowListener(WorkflowListener listener) { observationManager.addWorkflowListener(listener); } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof WorklowTypeRegistration) { WorklowTypeRegistration registration = (WorklowTypeRegistration) bean; registerWorkflowType(registration); logger.info("Registering workflow type \"" + registration.getType() + "\" with definition \"" + registration.getDefinition() + "\" and permissions: " + registration.getPermissions()); } return bean; } public void setJcrTemplate(JCRTemplate jcrTemplate) { this.jcrTemplate = jcrTemplate; } public WorkflowObservationManager getObservationManager() { return observationManager; } // TODO: implement JahiaAfterInitializationService instead of relying on invocation of this method from JBPM6WorkflowProvider. public synchronized void initAfterAllServicesAreStarted() throws JahiaInitializationException { servicesStarted = true; registerWorkflowTypes(); } /** * Returns the permission name, required to start the workflow of the specified type. * * @param worklowTypeRegistration * the workflow type registration object * @return the permission name, required to start the workflow of the specified type */ private String getPermissionForStart(WorklowTypeRegistration worklowTypeRegistration) { String startPermission = null; if (worklowTypeRegistration != null && worklowTypeRegistration.getPermissions() != null) { startPermission = worklowTypeRegistration.getPermissions().get(START_ROLE); if (startPermission != null) { int pos = startPermission.lastIndexOf('/'); if (pos != -1 && pos < startPermission.length() - 1) { startPermission = startPermission.substring(pos); } } } return startPermission; } }