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);
diff --git a/source/java/org/alfresco/web/bean/repository/TransientNode.java b/source/java/org/alfresco/web/bean/repository/TransientNode.java
new file mode 100644
index 0000000000..8ef05da567
--- /dev/null
+++ b/source/java/org/alfresco/web/bean/repository/TransientNode.java
@@ -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.
+ *
+ * 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.
+ *
+ * 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 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 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();
+ 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();
+ }
+}
diff --git a/source/java/org/alfresco/web/bean/wizard/WizardManager.java b/source/java/org/alfresco/web/bean/wizard/WizardManager.java
index f708413da4..a967648627 100644
--- a/source/java/org/alfresco/web/bean/wizard/WizardManager.java
+++ b/source/java/org/alfresco/web/bean/wizard/WizardManager.java
@@ -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
diff --git a/source/java/org/alfresco/web/bean/workflow/StartWorkflowWizard.java b/source/java/org/alfresco/web/bean/workflow/StartWorkflowWizard.java
new file mode 100644
index 0000000000..6393ebd320
--- /dev/null
+++ b/source/java/org/alfresco/web/bean/workflow/StartWorkflowWizard.java
@@ -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 availableWorkflows;
+ protected Map 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 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 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 getStartableWorkflows()
+ {
+ if (this.availableWorkflows == null)
+ {
+ this.availableWorkflows = new ArrayList(4);
+ this.workflows = new HashMap(4);
+
+ List 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 prepareTaskParams()
+ {
+ Map params = new HashMap();
+
+ // 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 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> assocs = this.startTaskNode.getAddedAssociations();
+ for (String assocName : assocs.keySet())
+ {
+ QName assocQName = Repository.resolveToQName(assocName);
+
+ // get the associations added and create list of targets
+ Map addedAssocs = assocs.get(assocName);
+ List targets = new ArrayList(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 params = new HashMap(1);
+// params.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), reviewer);
+
+
+ return params;
+ }
+}
diff --git a/source/java/org/alfresco/web/ui/repo/component/property/UIPropertySheet.java b/source/java/org/alfresco/web/ui/repo/component/property/UIPropertySheet.java
index ea3acc89a5..d60b492400 100644
--- a/source/java/org/alfresco/web/ui/repo/component/property/UIPropertySheet.java
+++ b/source/java/org/alfresco/web/ui/repo/component/property/UIPropertySheet.java
@@ -35,6 +35,7 @@ import org.alfresco.config.Config;
import org.alfresco.config.ConfigLookupContext;
import org.alfresco.config.ConfigService;
import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
import org.alfresco.web.app.Application;
import org.alfresco.web.app.servlet.FacesHelper;
import org.alfresco.web.bean.repository.Node;
@@ -626,8 +627,13 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
// create the property component
UIProperty propComp = (UIProperty)context.getApplication().
createComponent(RepoConstants.ALFRESCO_FACES_PROPERTY);
- FacesHelper.setupComponentId(context, propComp, PROP_ID_PREFIX + propertyName);
- propComp.setName(propertyName);
+
+ // get the property name in it's prefix form
+ QName qname = QName.createQName(propertyName);
+ String prefixPropName = qname.toPrefixString();
+
+ FacesHelper.setupComponentId(context, propComp, PROP_ID_PREFIX + prefixPropName);
+ propComp.setName(prefixPropName);
// if this property sheet is set as read only, set all properties to read only
if (isReadOnly())
@@ -642,7 +648,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
if (logger.isDebugEnabled())
logger.debug("Created property component " + propComp + "(" +
propComp.getClientId(context) +
- ") for '" + propertyName +
+ ") for '" + prefixPropName +
"' and added it to property sheet " + this);
}
@@ -654,8 +660,13 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
String assocName = (String)iter.next();
UIAssociation assocComp = (UIAssociation)context.getApplication().
createComponent(RepoConstants.ALFRESCO_FACES_ASSOCIATION);
- FacesHelper.setupComponentId(context, assocComp, ASSOC_ID_PREFIX + assocName);
- assocComp.setName(assocName);
+
+ // get the association name in it's prefix form
+ QName qname = QName.createQName(assocName);
+ String prefixAssocName = qname.toPrefixString();
+
+ FacesHelper.setupComponentId(context, assocComp, ASSOC_ID_PREFIX + prefixAssocName);
+ assocComp.setName(prefixAssocName);
// if this property sheet is set as read only, set all properties to read only
if (isReadOnly())
@@ -670,7 +681,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
if (logger.isDebugEnabled())
logger.debug("Created association component " + assocComp + "(" +
assocComp.getClientId(context) +
- ") for '" + assocName +
+ ") for '" + prefixAssocName +
"' and added it to property sheet " + this);
}
diff --git a/source/web/WEB-INF/faces-config-beans.xml b/source/web/WEB-INF/faces-config-beans.xml
index 6cd0af3096..d79b3e25bc 100644
--- a/source/web/WEB-INF/faces-config-beans.xml
+++ b/source/web/WEB-INF/faces-config-beans.xml
@@ -1739,6 +1739,44 @@
+
+ StartWorkflowWizard
+ org.alfresco.web.bean.workflow.StartWorkflowWizard
+ session
+
+ nodeService
+ #{NodeService}
+
+
+ fileFolderService
+ #{FileFolderService}
+
+
+ searchService
+ #{SearchService}
+
+
+ navigator
+ #{NavigationBean}
+
+
+ browseBean
+ #{BrowseBean}
+
+
+ dictionaryService
+ #{DictionaryService}
+
+
+ namespaceService
+ #{NamespaceService}
+
+
+ workflowService
+ #{WorkflowService}
+
+
+
diff --git a/source/web/jsp/workflow/start-workflow-wizard/choose-workflow.jsp b/source/web/jsp/workflow/start-workflow-wizard/choose-workflow.jsp
new file mode 100644
index 0000000000..d57f91f593
--- /dev/null
+++ b/source/web/jsp/workflow/start-workflow-wizard/choose-workflow.jsp
@@ -0,0 +1,28 @@
+<%--
+ Copyright (C) 2005 Alfresco, Inc.
+
+ Licensed under the Mozilla Public License version 1.1
+ with a permitted attribution clause. You may obtain a
+ copy of the License at
+
+ http://www.alfresco.org/legal/license.txt
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ either express or implied. See the License for the specific
+ language governing permissions and limitations under the
+ License.
+--%>
+<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
+<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
+<%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %>
+<%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %>
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/source/web/jsp/workflow/start-workflow-wizard/workflow-options.jsp b/source/web/jsp/workflow/start-workflow-wizard/workflow-options.jsp
new file mode 100644
index 0000000000..ef6e0942ff
--- /dev/null
+++ b/source/web/jsp/workflow/start-workflow-wizard/workflow-options.jsp
@@ -0,0 +1,44 @@
+<%--
+ Copyright (C) 2005 Alfresco, Inc.
+
+ Licensed under the Mozilla Public License version 1.1
+ with a permitted attribution clause. You may obtain a
+ copy of the License at
+
+ http://www.alfresco.org/legal/license.txt
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ either express or implied. See the License for the specific
+ language governing permissions and limitations under the
+ License.
+--%>
+<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
+<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
+<%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %>
+<%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %>
+
+<%@ page import="org.alfresco.web.ui.common.PanelGenerator" %>
+
+
+
+ <%PanelGenerator.generatePanelStart(out, request.getContextPath(), "yellowInner", "#ffffcc");%>
+
+ <%PanelGenerator.generatePanelEnd(out, request.getContextPath(), "yellowInner");%>
+
+
+
+
+
+
+
\ No newline at end of file