First cut of the start workflow wizard

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@3483 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Gavin Cornwell
2006-08-11 12:22:18 +00:00
parent 60cf6056c5
commit 65f394a878
13 changed files with 663 additions and 31 deletions

View File

@@ -36,8 +36,6 @@ import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.web.app.Application;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Lighweight client side representation of a node held in the repository.
@@ -48,28 +46,25 @@ public class Node implements Serializable
{
private static final long serialVersionUID = 3544390322739034169L;
protected static final Log logger = LogFactory.getLog(Node.class);
protected NodeRef nodeRef;
private String name;
private QName type;
private String path;
private String id;
private Set<QName> aspects = null;
private Map<String, Boolean> permissions;
private Boolean locked = null;
private Boolean workingCopyOwner = null;
protected String name;
protected QName type;
protected String path;
protected String id;
protected Set<QName> aspects = null;
protected Map<String, Boolean> permissions;
protected Boolean locked = null;
protected Boolean workingCopyOwner = null;
protected QNameNodeMap<String, Object> properties;
protected boolean propsRetrieved = false;
protected ServiceRegistry services = null;
protected boolean childAssocsRetrieved = false;
protected QNameNodeMap childAssociations;
protected boolean assocsRetrieved = false;
protected QNameNodeMap associations;
private boolean childAssocsRetrieved = false;
private QNameNodeMap childAssociations;
private Map<String, Map<String, ChildAssociationRef>> childAssociationsAdded;
private Map<String, Map<String, ChildAssociationRef>> childAssociationsRemoved;
private boolean assocsRetrieved = false;
private QNameNodeMap associations;
private Map<String, Map<String, AssociationRef>> associationsAdded;
private Map<String, Map<String, AssociationRef>> associationsRemoved;
@@ -94,7 +89,7 @@ public class Node implements Serializable
/**
* @return All the properties known about this node.
*/
public Map<String, Object> getProperties()
public final Map<String, Object> getProperties()
{
if (this.propsRetrieved == false)
{
@@ -120,7 +115,7 @@ public class Node implements Serializable
{
if (this.assocsRetrieved == false)
{
associations = new QNameNodeMap(getServiceRegistry().getNamespaceService(), this);
this.associations = new QNameNodeMap(getServiceRegistry().getNamespaceService(), this);
List<AssociationRef> assocs = getServiceRegistry().getNodeService().getTargetAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL);
@@ -341,7 +336,7 @@ public class Node implements Serializable
*
* @return true if the permission is applied to the node for this user, false otherwise
*/
public final boolean hasPermission(String permission)
public boolean hasPermission(String permission)
{
Boolean valid = null;
if (permissions != null)

View File

@@ -16,15 +16,11 @@
*/
package org.alfresco.web.bean.repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.alfresco.service.namespace.NamespacePrefixResolver;
import org.alfresco.service.namespace.QNameMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A extension of the repo QNameMap to provide custom property resolving support for Node wrappers.
@@ -86,6 +82,7 @@ public final class QNameNodeMap<K,V> extends QNameMap implements Map, Cloneable
/**
* @see java.util.Map#get(java.lang.Object)
*/
@SuppressWarnings("unchecked")
public Object get(Object key)
{
String qnameKey = Repository.resolveToQNameString(key.toString());
@@ -120,6 +117,7 @@ public final class QNameNodeMap<K,V> extends QNameMap implements Map, Cloneable
/**
* Shallow copy the map by copying keys and values into a new QNameNodeMap
*/
@SuppressWarnings("unchecked")
public Object clone()
{
QNameNodeMap map = new QNameNodeMap(this.resolver, this.parent);

View File

@@ -0,0 +1,141 @@
package org.alfresco.web.bean.repository;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID;
/**
* Represents a transient node i.e. it is not and will not be present in the repository.
* <p>
* This type of node is typically used to drive the property sheet when data collection
* is required for a type but the node does not need to be stored in the repository. An
* example use is the workflow, transient nodes are used to collect workitem metadata.
*
* @author gavinc
*/
public class TransientNode extends Node
{
private static final long serialVersionUID = 2140554155948154106L;
/**
* Constructor.
* <p>
* NOTE: The name is NOT automatically added to the map of properties,
* if you need the name of this node to be in the map then add it to
* the map passed in to this constructor.
*
* @param type The type this node will represent
* @param name The name of the node
* @param data The properties and associations this node will have
*/
public TransientNode(QName type, String name, Map<QName, Serializable> data)
{
// create a dummy NodeRef to pass to the constructor
super(new NodeRef(Repository.getStoreRef(), GUID.generate()));
this.type = type;
this.name = name;
// initialise the node
initNode(data);
}
/**
* Initialises the node.
*
* @param data The properties and associations to initialise the node with
*/
protected void initNode(Map<QName, Serializable> data)
{
// setup the transient node so that the super class methods work
// and do not need to go back to the repository
DictionaryService ddService = this.getServiceRegistry().getDictionaryService();
// marshall the given properties and associations into the internal maps
this.associations = new QNameNodeMap(getServiceRegistry().getNamespaceService(), this);
this.childAssociations = new QNameNodeMap(getServiceRegistry().getNamespaceService(), this);
if (data != null)
{
// go through all data items and allocate to the correct internal list
for (QName item : data.keySet())
{
PropertyDefinition propDef = ddService.getProperty(item);
if (propDef != null)
{
this.properties.put(item, data.get(item));
}
else
{
// see if the item is either type of association
AssociationDefinition assocDef = ddService.getAssociation(item);
if (assocDef != null)
{
if (assocDef.isChild())
{
this.childAssociations.put(item, data.get(item));
}
else
{
this.associations.put(item, data.get(item));
}
}
}
}
}
// show that the maps have been initialised
this.propsRetrieved = true;
this.assocsRetrieved = true;
this.childAssocsRetrieved = true;
// setup the list of aspects the node would have
TypeDefinition typeDef = ddService.getType(this.type);
if (typeDef == null)
{
throw new AlfrescoRuntimeException("Failed to find type definition for start task: " + this.type);
}
this.aspects = new HashSet<QName>();
for (AspectDefinition aspectDef : typeDef.getDefaultAspects())
{
this.aspects.add(aspectDef.getName());
}
// setup remaining variables
this.path = "";
this.locked = Boolean.FALSE;
this.workingCopyOwner = Boolean.FALSE;
}
@Override
public boolean hasPermission(String permission)
{
return true;
}
@Override
public void reset()
{
// don't reset anything otherwise we'll lose our data
// with no way of getting it back!!
}
@Override
public String toString()
{
return "Transient node of type: " + getType() +
"\nProperties: " + this.getProperties().toString();
}
}

View File

@@ -187,6 +187,18 @@ public class WizardManager
{
return Integer.toString(this.currentStep);
}
/**
* Returns the name of the current step, wizards should use
* the name of the step rather than the step number to discover
* the position as extra steps can be added via configuration.
*
* @return The name of the current step
*/
public String getCurrentStepName()
{
return ((StepConfig)this.steps.get(this.currentStep-1)).getName();
}
/**
* Returns a list of UIListItems representing the steps of the wizard

View File

@@ -0,0 +1,286 @@
package org.alfresco.web.bean.workflow;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowPath;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition;
import org.alfresco.service.cmr.workflow.WorkflowTaskState;
import org.alfresco.service.namespace.QName;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.bean.repository.TransientNode;
import org.alfresco.web.bean.wizard.BaseWizardBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Bean implementation for the Start Workflow Wizard.
*
* @author gavinc
*/
public class StartWorkflowWizard extends BaseWizardBean
{
protected String selectedWorkflow;
protected List<SelectItem> availableWorkflows;
protected Map<String, WorkflowDefinition> workflows;
protected WorkflowService workflowService;
protected Node startTaskNode;
protected boolean nextButtonDisabled = false;
private static final Log logger = LogFactory.getLog(StartWorkflowWizard.class);
// ------------------------------------------------------------------------------
// Wizard implementation
@Override
public void init(Map<String, String> parameters)
{
super.init(parameters);
// reset the selected workflow
if (this.availableWorkflows != null && this.availableWorkflows.size() > 0)
{
this.selectedWorkflow = (String)this.availableWorkflows.get(0).getValue();
}
else
{
this.selectedWorkflow = null;
}
this.startTaskNode = null;
}
@Override
protected String finishImpl(FacesContext context, String outcome)
throws Exception
{
// TODO: Deal with workflows that don't require any data
if (logger.isDebugEnabled())
logger.debug("Starting workflow with params: " + this.startTaskNode.getProperties());
// start the workflow to get access to the start task
WorkflowPath path = this.workflowService.startWorkflow(this.selectedWorkflow, prepareTaskParams());
if (path != null)
{
// extract the start task
List<WorkflowTask> tasks = this.workflowService.getTasksForWorkflowPath(path.id);
if (tasks.size() == 1)
{
WorkflowTask startTask = tasks.get(0);
if (logger.isDebugEnabled())
logger.debug("Found start task:" + startTask);
if (startTask.state == WorkflowTaskState.IN_PROGRESS)
{
// end the start task to trigger the first 'proper'
// task in the workflow
this.workflowService.endTask(startTask.id, null);
}
}
if (logger.isDebugEnabled())
logger.debug("Started workflow: " + this.selectedWorkflow);
}
return outcome;
}
@Override
public String next()
{
String stepName = Application.getWizardManager().getCurrentStepName();
if ("options".equals(stepName) && this.startTaskNode == null)
{
// retrieve the start task for the selected workflow, get the task
// definition and create a transient node to allow the property
// sheet to collect the required data.
WorkflowDefinition flowDef = this.workflows.get(this.selectedWorkflow);
if (logger.isDebugEnabled())
logger.debug("Starting workflow: "+ flowDef);
WorkflowTaskDefinition taskDef = flowDef.startTaskDefinition;
if (taskDef != null)
{
if (logger.isDebugEnabled())
logger.debug("Start task definition: " + taskDef);
// create an instance of a task from the data dictionary
this.startTaskNode = new TransientNode(taskDef.metadata.getName(),
"task_" + System.currentTimeMillis(), null);
if (logger.isDebugEnabled())
logger.debug("Created node for task: " + this.startTaskNode);
}
}
return null;
}
@Override
public boolean getNextButtonDisabled()
{
return this.nextButtonDisabled;
}
// ------------------------------------------------------------------------------
// Bean Getters and Setters
/**
* Returns the workflow selected by the user
*
* @return The selected workflow
*/
public String getSelectedWorkflow()
{
return selectedWorkflow;
}
/**
* Sets the selected workflow
*
* @param selectedWorkflow The workflow selected
*/
public void setSelectedWorkflow(String selectedWorkflow)
{
this.selectedWorkflow = selectedWorkflow;
}
/**
* Returns the Node representing the start task metadata required
*
* @return The Node for the start task
*/
public Node getTaskMetadataNode()
{
return this.startTaskNode;
}
/**
* @return Returns the summary data for the wizard.
*/
public String getSummary()
{
ResourceBundle bundle = Application.getBundle(FacesContext.getCurrentInstance());
String workflowName = null;
for (SelectItem item : this.availableWorkflows)
{
if (item.getValue().equals(this.selectedWorkflow))
{
workflowName = item.getLabel();
break;
}
}
return buildSummary(
new String[] {bundle.getString("start_workflow")},
new String[] {workflowName});
}
/**
* Returns a list of workflows that can be started.
*
* @return List of SelectItem objects representing the workflows
*/
public List<SelectItem> getStartableWorkflows()
{
if (this.availableWorkflows == null)
{
this.availableWorkflows = new ArrayList<SelectItem>(4);
this.workflows = new HashMap<String, WorkflowDefinition>(4);
List<WorkflowDefinition> workflowDefs = this.workflowService.getDefinitions();
for (WorkflowDefinition workflowDef : workflowDefs)
{
this.availableWorkflows.add(new SelectItem(workflowDef.id, workflowDef.name));
this.workflows.put(workflowDef.id, workflowDef);
}
// set the initial selected workflow to the first in the list, unless there are no
// workflows, in which disable the next button
if (this.availableWorkflows.size() > 0)
{
this.selectedWorkflow = (String)this.availableWorkflows.get(0).getValue();
}
else
{
this.nextButtonDisabled = true;
}
}
return availableWorkflows;
}
/**
* Sets the workflow service to use
*
* @param workflowService WorkflowService instance
*/
public void setWorkflowService(WorkflowService workflowService)
{
this.workflowService = workflowService;
}
protected Map<QName, Serializable> prepareTaskParams()
{
Map<QName, Serializable> params = new HashMap<QName, Serializable>();
// marshal the properties and associations captured by the property sheet
// back into a Map to pass to the workflow service
// go through all the properties in the transient node and add them to
// params map
Map<String, Object> props = this.startTaskNode.getProperties();
for (String propName : props.keySet())
{
QName propQName = Repository.resolveToQName(propName);
params.put(propQName, (Serializable)props.get(propName));
}
// go through any associations that have been added to the start task
// and build a list of NodeRefs representing the targets
Map<String, Map<String, AssociationRef>> assocs = this.startTaskNode.getAddedAssociations();
for (String assocName : assocs.keySet())
{
QName assocQName = Repository.resolveToQName(assocName);
// get the associations added and create list of targets
Map<String, AssociationRef> addedAssocs = assocs.get(assocName);
List<NodeRef> targets = new ArrayList<NodeRef>(addedAssocs.size());
for (AssociationRef assoc : addedAssocs.values())
{
targets.add(assoc.getTargetRef());
}
// add the targets for this particular association
params.put(assocQName, (Serializable)targets);
}
// String reviewer = (String)this.startTaskNode.getProperties().get(
// ContentModel.PROP_NAME);
// Map<QName, Serializable> params = new HashMap<QName, Serializable>(1);
// params.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), reviewer);
return params;
}
}