diff --git a/config/alfresco/messages/webclient.properties b/config/alfresco/messages/webclient.properties index 59326d4573..71c0b8ae2d 100644 --- a/config/alfresco/messages/webclient.properties +++ b/config/alfresco/messages/webclient.properties @@ -919,7 +919,12 @@ start_workflow_no_metadata=There is no metadata to collect for this particular w users_and_roles=Users and their Roles manage_workitem=Manage WorkItem manage_workitem_title=Manage WorkItem -manage_workitem_desc=This dialog allows the workitem to be managed +manage_workitem_desc=This dialog allows the work item to be managed +id=Id +status=Status +my_workitems_todo_title=My Work Items To Do +my_workitems_todo_desc=List of your workflow items still to complete +due_date=Due Date reassign=Reassign # Admin Console messages diff --git a/config/alfresco/web-client-config-dialogs.xml b/config/alfresco/web-client-config-dialogs.xml index e34558df8e..1b6028ba91 100644 --- a/config/alfresco/web-client-config-dialogs.xml +++ b/config/alfresco/web-client-config-dialogs.xml @@ -38,21 +38,6 @@ icon="/images/icons/delete_large.gif" title-id="delete_file" description-id="delete_file_info" /> - - - - - - - diff --git a/config/alfresco/web-client-config-wizards.xml b/config/alfresco/web-client-config-wizards.xml index 1936302ae0..a3aad002c2 100644 --- a/config/alfresco/web-client-config-wizards.xml +++ b/config/alfresco/web-client-config-wizards.xml @@ -182,31 +182,6 @@ - - - - - - - - - - - - diff --git a/config/alfresco/web-client-config-workflow.xml b/config/alfresco/web-client-config-workflow.xml index 1202b210a8..2bb90ba827 100644 --- a/config/alfresco/web-client-config-workflow.xml +++ b/config/alfresco/web-client-config-workflow.xml @@ -4,6 +4,18 @@ + + + + + + + + + + @@ -29,12 +41,66 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/web-client-config.xml b/config/alfresco/web-client-config.xml index f36a592dbe..348988f768 100644 --- a/config/alfresco/web-client-config.xml +++ b/config/alfresco/web-client-config.xml @@ -188,14 +188,14 @@ + + - - + * This type of node is typically used to drive rich lists where the Map implementation + * is required for sorting columns. + *

+ * + * @author gavinc + */ +public class TransientMapNode extends TransientNode implements Map +{ + private static final long serialVersionUID = 1120307465342597322L; + + /** + * 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 TransientMapNode(QName type, String name, Map data) + { + super(type, name, data); + } + + @Override + public String toString() + { + return "Transient map node of type: " + getType() + + "\nProperties: " + this.getProperties().toString(); + } + + // ------------------------------------------------------------------------------ + // Map implementation - allows the Node bean to be accessed using JSF expression syntax + + /** + * @see java.util.Map#clear() + */ + public void clear() + { + getProperties().clear(); + } + + /** + * @see java.util.Map#containsKey(java.lang.Object) + */ + public boolean containsKey(Object key) + { + return getProperties().containsKey(key); + } + + /** + * @see java.util.Map#containsValue(java.lang.Object) + */ + public boolean containsValue(Object value) + { + return getProperties().containsKey(value); + } + + /** + * @see java.util.Map#entrySet() + */ + @SuppressWarnings("unchecked") + public Set entrySet() + { + return getProperties().entrySet(); + } + + /** + * @see java.util.Map#get(java.lang.Object) + */ + public Object get(Object key) + { + return getProperties().get(key); + } + + /** + * @see java.util.Map#isEmpty() + */ + public boolean isEmpty() + { + return getProperties().isEmpty(); + } + + /** + * @see java.util.Map#keySet() + */ + @SuppressWarnings("unchecked") + public Set keySet() + { + return getProperties().keySet(); + } + + /** + * @see java.util.Map#put(K, V) + */ + public Object put(String key, Object value) + { + return getProperties().put(key, value); + } + + /** + * @see java.util.Map#putAll(java.util.Map) + */ + @SuppressWarnings("unchecked") + public void putAll(Map t) + { + getProperties().putAll(t); + } + + /** + * @see java.util.Map#remove(java.lang.Object) + */ + public Object remove(Object key) + { + return getProperties().remove(key); + } + + /** + * @see java.util.Map#size() + */ + public int size() + { + return getProperties().size(); + } + + /** + * @see java.util.Map#values() + */ + @SuppressWarnings("unchecked") + public Collection values() + { + return getProperties().values(); + } +} diff --git a/source/java/org/alfresco/web/bean/repository/TransientNode.java b/source/java/org/alfresco/web/bean/repository/TransientNode.java index 8ef05da567..21dfbe340e 100644 --- a/source/java/org/alfresco/web/bean/repository/TransientNode.java +++ b/source/java/org/alfresco/web/bean/repository/TransientNode.java @@ -20,6 +20,7 @@ import org.alfresco.util.GUID; * 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 */ @@ -33,6 +34,7 @@ public class TransientNode extends Node * 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 diff --git a/source/java/org/alfresco/web/bean/workflow/ManageWorkItemDialog.java b/source/java/org/alfresco/web/bean/workflow/ManageWorkItemDialog.java index a25eea0e9b..75241e0144 100644 --- a/source/java/org/alfresco/web/bean/workflow/ManageWorkItemDialog.java +++ b/source/java/org/alfresco/web/bean/workflow/ManageWorkItemDialog.java @@ -1,12 +1,24 @@ package org.alfresco.web.bean.workflow; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import java.util.Map; import javax.faces.context.FacesContext; +import javax.transaction.UserTransaction; +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.namespace.QName; +import org.alfresco.web.app.Application; import org.alfresco.web.bean.dialog.BaseDialogBean; +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.config.DialogsConfigElement.DialogButtonConfig; +import org.alfresco.web.ui.common.Utils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -17,51 +29,186 @@ import org.apache.commons.logging.LogFactory; */ public class ManageWorkItemDialog extends BaseDialogBean { - private static final Log logger = LogFactory.getLog(ManageWorkItemDialog.class); + protected WorkflowService workflowService; + protected Node workItemNode; + protected WorkflowTask workItem; + protected String[] transitions; + + protected static final String ID_PREFIX = "transition_"; + protected static final String CLIENT_ID_PREFIX = "dialog:" + ID_PREFIX; + private static final Log logger = LogFactory.getLog(ManageWorkItemDialog.class); + // ------------------------------------------------------------------------------ // Dialog implementation + + @Override + public void init(Map parameters) + { + super.init(parameters); + + String taskId = this.parameters.get("id"); + this.workItem = this.workflowService.getTaskById(taskId); + + if (this.workItem != null) + { + // setup a transient node to represent the work item we're managing + WorkflowTaskDefinition taskDef = this.workItem.definition; + this.workItemNode = new TransientNode(taskDef.metadata.getName(), + "task_" + System.currentTimeMillis(), this.workItem.properties); + + if (logger.isDebugEnabled()) + logger.debug("Created node for work item: " + this.workItemNode); + } + } @Override protected String finishImpl(FacesContext context, String outcome) throws Exception { - return null; + if (logger.isDebugEnabled()) + logger.debug("Saving work item with params: " + this.workItemNode.getProperties()); + + // prepare the edited parameters for saving + Map params = WorkflowBean.prepareWorkItemParams(this.workItemNode); + + // update the task with the updated parameters + this.workflowService.updateTask(this.workItem.id, params, null, null); + + return outcome; } - + @Override public List getAdditionalButtons() { - List buttons = new ArrayList(1); - buttons.add(new DialogButtonConfig("reassign-button", - "Reassign", null, "#{ManageWorkItemDialog.reassign}", "false", null)); + List buttons = null; + + if (this.workItem != null) + { + // get the transitions available from this work item and + // show them in the dialog as additional buttons + this.transitions = this.workItem.path.node.transitions; + + if (this.transitions != null) + { + buttons = new ArrayList(this.transitions.length); + + for (String trans : this.transitions) + { + // TODO: Tidy this up when the service returns the list of labels + String label = trans; + if (label.length() == 0) + { + label = "Done"; + } + + buttons.add(new DialogButtonConfig(ID_PREFIX + trans, label, null, + "#{DialogManager.bean.transition}", "false", null)); + } + } + } - return buttons; + return buttons; + } + + @Override + public String getFinishButtonLabel() + { + return Application.getMessage(FacesContext.getCurrentInstance(), "save"); } + @Override + public boolean getFinishButtonDisabled() + { + return false; + } + // ------------------------------------------------------------------------------ // Event handlers - - public void approve() + + @SuppressWarnings("unused") + public String transition() { - logger.info("approve button was pressed"); - } - - public void reject() - { - logger.info("reject button was pressed"); - } - - public void reassign() - { - logger.info("reassign button was pressed"); + String outcome = getDefaultFinishOutcome(); + + // to find out which transition button was pressed we need + // to look for the button's id in the request parameters, + // the first non-null result is the button that was pressed. + FacesContext context = FacesContext.getCurrentInstance(); + Map reqParams = context.getExternalContext().getRequestParameterMap(); + + String selectedTransition = null; + for (String trans : this.transitions) + { + Object result = reqParams.get(CLIENT_ID_PREFIX + trans); + if (result != null) + { + // this was the button that was pressed + selectedTransition = trans; + break; + } + } + + if (selectedTransition != null) + { + UserTransaction tx = null; + + try + { + tx = Repository.getUserTransaction(context); + tx.begin(); + + // prepare the edited parameters for saving + Map params = WorkflowBean.prepareWorkItemParams(this.workItemNode); + + // update the task with the updated parameters + this.workflowService.updateTask(this.workItem.id, params, null, null); + + // signal the selected transition to the workflow task + this.workflowService.endTask(this.workItem.id, selectedTransition); + + // commit the changes + tx.commit(); + + if (logger.isDebugEnabled()) + logger.debug("Ended work item with transition: " + selectedTransition); + } + catch (Throwable e) + { + // reset the flag so we can re-attempt the operation + isFinished = false; + + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(formatErrorMessage(e)); + outcome = this.getErrorOutcome(e); + } + } + + return outcome; } // ------------------------------------------------------------------------------ // Bean Getters and Setters - - public boolean getApproveDisabled() + + /** + * Returns the Node representing the work item + * + * @return The node + */ + public Node getWorkItemNode() { - return true; + return this.workItemNode; + } + + /** + * Sets the workflow service to use + * + * @param workflowService + * WorkflowService instance + */ + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; } } diff --git a/source/java/org/alfresco/web/bean/workflow/StartWorkflowWizard.java b/source/java/org/alfresco/web/bean/workflow/StartWorkflowWizard.java index 6393ebd320..9a1300c250 100644 --- a/source/java/org/alfresco/web/bean/workflow/StartWorkflowWizard.java +++ b/source/java/org/alfresco/web/bean/workflow/StartWorkflowWizard.java @@ -1,6 +1,5 @@ package org.alfresco.web.bean.workflow; -import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -10,18 +9,14 @@ 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; @@ -74,7 +69,8 @@ public class StartWorkflowWizard extends BaseWizardBean 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()); + WorkflowPath path = this.workflowService.startWorkflow(this.selectedWorkflow, + WorkflowBean.prepareWorkItemParams(this.startTaskNode)); if (path != null) { // extract the start task @@ -239,48 +235,4 @@ public class StartWorkflowWizard extends BaseWizardBean { 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/bean/workflow/WorkflowBean.java b/source/java/org/alfresco/web/bean/workflow/WorkflowBean.java new file mode 100644 index 0000000000..cafb7e1e79 --- /dev/null +++ b/source/java/org/alfresco/web/bean/workflow/WorkflowBean.java @@ -0,0 +1,140 @@ +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 javax.faces.context.FacesContext; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +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.util.ISO9075; +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.TransientMapNode; +import org.alfresco.web.bean.repository.User; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Managed bean used for handling workflow related features + * + * @author gavinc + */ +public class WorkflowBean +{ + protected WorkflowService workflowService; + protected List workItems; + + private static final Log logger = LogFactory.getLog(WorkflowBean.class); + + // ------------------------------------------------------------------------------ + // Bean Getters and Setters + + public List getWorkItemsToDo() + { + // get the current username + FacesContext fc = FacesContext.getCurrentInstance(); + User user = Application.getCurrentUser(fc); + String userName = ISO9075.encode(user.getUserName()); + + // get the current in progress tasks for the current user + List tasks = this.workflowService.getAssignedTasks( + userName, WorkflowTaskState.IN_PROGRESS); + + // create a list of transient nodes to represent + this.workItems = new ArrayList(tasks.size()); + for (WorkflowTask task : tasks) + { + createWorkItem(task); + } + + return this.workItems; + } + + /** + * Sets the workflow service to use + * + * @param workflowService WorkflowService instance + */ + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + // ------------------------------------------------------------------------------ + // Helper methods + + public static Map prepareWorkItemParams(Node node) + { + 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 = node.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 = node.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); + } + + return params; + } + + /** + * Creates and populates a TransientNode to represent the given + * workflow task from the repository workflow engine + * + * @param task The task to create a representation of + */ + protected void createWorkItem(WorkflowTask task) + { + // get the type of the task + WorkflowTaskDefinition taskDef = task.definition; + + // create the basic transient node + TransientMapNode node = new TransientMapNode(taskDef.metadata.getName(), + task.name, task.properties); + + // add properties for the other useful metadata + node.getProperties().put(ContentModel.PROP_NAME.toString(), task.name); + node.getProperties().put("type", taskDef.metadata.getTitle()); + node.getProperties().put("id", task.id); + + if (logger.isDebugEnabled()) + logger.debug("Created node for work item with id '" + task.id + "' " + node); + + this.workItems.add(node); + } +} diff --git a/source/web/WEB-INF/faces-config-beans.xml b/source/web/WEB-INF/faces-config-beans.xml index d79b3e25bc..4f0504bcb6 100644 --- a/source/web/WEB-INF/faces-config-beans.xml +++ b/source/web/WEB-INF/faces-config-beans.xml @@ -1737,6 +1737,10 @@ namespaceService #{NamespaceService} + + workflowService + #{WorkflowService} + @@ -1777,6 +1781,16 @@ + + WorkflowBean + org.alfresco.web.bean.workflow.WorkflowBean + session + + workflowService + #{WorkflowService} + + + diff --git a/source/web/jsp/dashboards/dashlets/my-tasks-todo.png b/source/web/jsp/dashboards/dashlets/my-tasks-todo.png deleted file mode 100644 index e34ceee2aa..0000000000 Binary files a/source/web/jsp/dashboards/dashlets/my-tasks-todo.png and /dev/null differ diff --git a/source/web/jsp/dashboards/dashlets/tasklist-todo.jsp b/source/web/jsp/workflow/manage-workitem-dialog.jsp similarity index 78% rename from source/web/jsp/dashboards/dashlets/tasklist-todo.jsp rename to source/web/jsp/workflow/manage-workitem-dialog.jsp index 22d7f0c020..fe9e556ed1 100644 --- a/source/web/jsp/dashboards/dashlets/tasklist-todo.jsp +++ b/source/web/jsp/workflow/manage-workitem-dialog.jsp @@ -1,5 +1,5 @@ <%-- - Copyright (C) 2006 Alfresco, Inc. + Copyright (C) 2005 Alfresco, Inc. Licensed under the Mozilla Public License version 1.1 with a permitted attribution clause. You may obtain a @@ -19,4 +19,5 @@ <%@ 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/manage-workitem.jsp b/source/web/jsp/workflow/manage-workitem.jsp deleted file mode 100644 index b518813ad9..0000000000 --- a/source/web/jsp/workflow/manage-workitem.jsp +++ /dev/null @@ -1,28 +0,0 @@ -<%-- - 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/choose-workflow.jsp b/source/web/jsp/workflow/start-workflow-wizard/choose-workflow.jsp index d57f91f593..a3799bbefb 100644 --- a/source/web/jsp/workflow/start-workflow-wizard/choose-workflow.jsp +++ b/source/web/jsp/workflow/start-workflow-wizard/choose-workflow.jsp @@ -19,7 +19,7 @@ <%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %> <%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %> - + diff --git a/source/web/jsp/workflow/workitems-todo-dashlet.jsp b/source/web/jsp/workflow/workitems-todo-dashlet.jsp new file mode 100644 index 0000000000..59ec036ddb --- /dev/null +++ b/source/web/jsp/workflow/workitems-todo-dashlet.jsp @@ -0,0 +1,88 @@ +<%@ 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" %> + + + + <%-- Primary column for details view mode --%> + + + + + + + + + + + + + + + + + <%-- Description column --%> + + + + + + + + <%-- Type column --%> + + + + + + + + <%-- Due date column --%> + + + + + + + + + + <%-- Status column --%> + + + + + + + + <%-- Actions column --%> + <%-- + + + + + + + + + + + + + + --%> + + + + + \ No newline at end of file