Customization of a standard Magnolia workflow: segment your users!

Published on July 12, 2016 by Matteo Pelucco

Magnolia ships with a pre-configured 4-eyes workflow where all content must be approved by the same group of people, so called “Publishers”. This can cover 90% of projects, where the editorial team is not big enough to allow content segmentation or workflow escalation. But what happens when you are in a big project, you have a big editorial team and you have to segment your content workflow?



The standard approach would be:

  1. Download Magnolia source code
  2. Change the 4-eyes BPML2 diagram (you need Eclipse, jBPML plugin, …!)
  3. Inject your logic
  4. Deploy the new 4-eyes workflow it really necessary? Of course, not.

As usual in Magnolia, JCR configurations help us on this task.
We need to store the mapping (JCR path to workflow group) somewhere and one good place for that can be the publish task in the workflow-jbpm module.


With this mapping, any activation from /demo-project will be authorized by people belonging to the “demo-project-publishers” group and any /demo-features activation by people belonging to the “demo-features-publishers”. Just a note: since multiple group can be added to any activation, we’ve left “publishers” group on purpose, to be compliant to a standard Magnolia installation and to allow superuser to do (almost) everything on our activations.
That configuration is read by a Java class (for simplicity, ExtendedConfiguredPublicationTaskDefinition), that needs to be extended in order to handle the mapping settings we just added. Here's the Java code:


package com.matteopelucco.workflow;
import info.magnolia.module.workflow.jbpm.humantask.definition.ConfiguredPublicationTaskDefinition;
import java.util.LinkedList;
import java.util.List;
public class ExtendedConfiguredPublicationTaskDefinition extends ConfiguredPublicationTaskDefinition {
        List<GroupsMapping> groupsMapping = new LinkedList<GroupsMapping>();
        public List<GroupsMapping> getGroupsMapping() {
                return groupsMapping;
        public void setGroupsMapping(List<GroupsMapping> groupsMapping) {
                this.groupsMapping = groupsMapping;
        public void addGroupsMapping(GroupsMapping groupsMapping) {

And here's the mapping class, GroupsMapping:


package com.matteopelucco.workflow;
import java.util.LinkedList;
import java.util.List;
public class GroupsMapping {
        private List<String> groups = new LinkedList<String>();
        private String path;
        private String workspace;
        public String getPath() {
                return path;
        public void setPath(String path) {
                this.path = path;
        public String getWorkspace() {
                return workspace;
        public void setWorkspace(String workspace) {
                this.workspace = workspace;
        public List<String> getGroups() {
                return groups;
        public void setGroups(List<String> groups) {
                this.groups = groups;
        public void addGroups(String group) {

And finally, we can easily handle that with a resolver class, ExtendedPublicationTaskParameterResolver:


package com.matteopelucco.workflow;
import info.magnolia.context.Context;
import info.magnolia.module.workflow.api.WorkflowConstants;
import info.magnolia.module.workflow.jbpm.humantask.HumanTask;
import info.magnolia.module.workflow.jbpm.humantask.definition.PublicationTaskDefinition;
import info.magnolia.module.workflow.jbpm.humantask.parameter.PublicationTaskParameterResolver;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.kie.api.runtime.process.WorkItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExtendedPublicationTaskParameterResolver extends PublicationTaskParameterResolver {
        private static final Logger log = LoggerFactory.getLogger(ExtendedPublicationTaskParameterResolver.class);
        public ExtendedPublicationTaskParameterResolver(PublicationTaskDefinition definition) {
        public void setTaskParameters(HumanTask task, WorkItem workItem) {
                super.setTaskParameters(task, workItem);
                // EXTENDED LOGIC
                PublicationTaskDefinition definition = getDefinition();
                if (definition instanceof ExtendedConfiguredPublicationTaskDefinition) {
                        ExtendedConfiguredPublicationTaskDefinition extendedDefinition = (ExtendedConfiguredPublicationTaskDefinition) definition;
                        List<GroupsMapping> groupsMapping = extendedDefinition.getGroupsMapping();
                        forLoop: for (GroupsMapping groupsMap : groupsMapping) {
                                final String path = (String) task.getContent().get(Context.ATTRIBUTE_PATH);
                                final String workspace = (String) task.getContent().get(Context.ATTRIBUTE_REPOSITORY);
                                if (StringUtils.equals(groupsMap.getWorkspace(), workspace) && StringUtils.startsWith(path, groupsMap.getPath())) {
                                        // mapping found!
                                        List<String> groupIds = groupsMap.getGroups();
                                        continue forLoop;

Let’s try that. We need to start 2 publications: one for each site tree:




As recommended, superuser can see both of them..



.. but what can other users see? Try to log in with Frank, a new user belonging to demo-features-publishers:



He now only sees the relevant publication. The other one does not appear to this user.



If you want to see what happens behind the scenes (aka: into JCR), just create a content app that lists all Task repository: everything is there:




Quick. And clean.


{{item.userId}}   {{item.timestamp | timestampToDate}}

About the author Matteo Pelucco

Matteo Pelucco is a senior software engineer with a deep focus on web technologies. Since 2007, he has worked for Tinext, a leading-edge technology company that has delivered, deployed and maintained more than 100 successful Magnolia projects. Matteo is passionate about html, javascript, ajax, mobile development, server side scripting (Java, Php, Coldfusion), database programming (Hibernate, JPA, MySQL, PostgreSQL), middleware integration, ORM, DAO.. and much more.

See all posts on Matteo Pelucco