Java tutorial
/* Copyright (C) 2007-2011 BlueXML - www.bluexml.com 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/>. */ package com.bluexml.xforms.actions; import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.ServletException; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.QName; import org.apache.commons.lang.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import com.bluexml.side.form.utils.DOMUtil; import com.bluexml.xforms.controller.beans.PageInfoBean; import com.bluexml.xforms.controller.beans.PersistFormResultBean; import com.bluexml.xforms.controller.beans.RedirectionBean; import com.bluexml.xforms.controller.beans.WorkflowTaskInfoBean; import com.bluexml.xforms.controller.binding.GenericAttribute; import com.bluexml.xforms.controller.binding.GenericClass; import com.bluexml.xforms.controller.binding.ValueType; import com.bluexml.xforms.controller.navigation.Page; import com.bluexml.xforms.messages.MsgId; /** * @author davidabad */ public abstract class AbstractWorkflowAction extends AbstractWriteAction { /** The separator for the compound string that contains info about a task */ protected static final String TASK_SEPARATOR = "{::}"; protected static final int TASK_SEPARATOR_LENGTH = TASK_SEPARATOR.length(); protected static final String TRANSITION_NAME = MsgId.INT_ACT_PARAM_EXEC_ACTION.getText(); protected Page currentPage; protected String userName; /* * (non-Javadoc) * @see com.bluexml.xforms.actions.AbstractAction#submit() */ @Override public void submit() throws ServletException { if (controller.isInStandaloneMode()) { String msg = "The Alfresco Controller is in standalone mode. Some actions are unavailable"; navigationPath.setStatusMsg(msg); throw new ServletException(msg); } Page currentPage = navigationPath.peekCurrentPage(); // do the work TransitionResultBean resultBean = submitWork(); // redirect the client to the appropriate next page String msg = navigationPath.getStatusMsg(); String URLsuffix = MsgId.PARAM_STATUS_MSG + "=" + StringUtils.trimToEmpty(msg); if (resultBean.isSuccess()) { redirectSuccess(currentPage, resultBean, URLsuffix); } else { redirectFailure(currentPage, URLsuffix); } } protected abstract TransitionResultBean submitWork() throws ServletException; /** * Deals with redirecting the client in case the transition succeeded. * * @param currentPage * @param resultBean * @param URLsuffix */ protected void redirectSuccess(Page currentPage, TransitionResultBean resultBean, String URLsuffix) { Map<String, String> initParams = currentPage.getInitParams(); String nextPage = null; // // decide whether to append parameters to the URL boolean foundInParams = initParams.containsKey(MsgId.PARAM_SKIP_ADDITIONAL_INFO.getText()); boolean skipInfo = false; String formName = currentPage.getFormName(); if (foundInParams) { skipInfo = (StringUtils.equals(initParams.get(MsgId.PARAM_SKIP_ADDITIONAL_INFO.getText()), "true")); } else { // #1656 // there may be something specified in the Xtension property of this form skipInfo = controller.getXtensionSkipAdditionalInfo(formName, currentPage.getFormType()); } // // compute the suffix (parameters string), if any. String suffix = ""; if (skipInfo == false) { suffix += URLsuffix; // normally, the instance and process ids are available suffix = suffix + "&" + MsgId.PARAM_WORKFLOW_PROCESS_ID + "=" + currentPage.getWkflwProcessId(); suffix = suffix + "&" + MsgId.PARAM_WORKFLOW_INSTANCE_ID + "=" + currentPage.getWkflwInstanceId(); } // // determine the redirection URL if any nextPage = initParams.get(MsgId.PARAM_SUCCESS_PAGE.getText()); if (nextPage == null) { // #1656 // there may be something specified in the Xtension property of this form nextPage = controller.getXtensionSuccessPage(formName); } // // do the redirection if (nextPage != null) { // go to any url that was specified redirectToClientURL(formName, nextPage, suffix, skipInfo); } else { // we get to decide where to redirect redirectToWorkflowForm(currentPage, resultBean, suffix); } } /** * @param currentPage * @param resultBean * @param URLsuffix */ protected void redirectToWorkflowForm(Page currentPage, TransitionResultBean resultBean, String URLsuffix) { String nextFormName; String location; Map<String, String> initParams = currentPage.getInitParams(); RedirectionBean bean = controller.getWorkflowRedirectionBean(currentPage.getFormName()); if (bean != null) { if (bean.isAutoAdvance()) { // if autoAdvancing, get the next form from the tasks if (resultBean.getTasks() != null && (resultBean.getTasks().size() > 0)) { String taskInfoString = resultBean.getTasks().get(0); String nextTaskName = getNameFromTaskIdNameTitle(taskInfoString); nextFormName = controller.getWorkflowFormNameFromTask(nextTaskName); location = buildNextFormUrl(nextFormName, initParams, URLsuffix, true); super.redirectClient(location); return; } } else { // go to the specified address if one is provided if (StringUtils.trimToNull(bean.getTargetUrl()) != null) { location = addRedirectionParameters(bean.getTargetUrl(), bean.isAutoAdvance(), bean.isAddParams(), initParams, URLsuffix); super.redirectClient(location); return; } } } // default case: reload the same page setSubmissionDefaultLocation(getServletURL(), result); } /** * Appends parameters that may be useful to the given target URL. * * @param targetUrl * @return */ protected String addRedirectionParameters(String targetUrl, boolean autoAdvance, boolean addParams, Map<String, String> initParams, String suffix) { if (addParams == false) { return targetUrl; } boolean first = true; StringBuffer outParamsStr = new StringBuffer(suffix); for (String param : initParams.keySet()) { if (isParameterToSkip(param)) { break; } first = addParam(outParamsStr, first, param, initParams.get(param)); } if (autoAdvance) { first = addParam(outParamsStr, first, MsgId.PARAM_AUTO_ADVANCE.getText(), "true"); } // redirect to specified URL String infixe = (outParamsStr.indexOf("?") == -1) ? "?" : "&"; String location = targetUrl + infixe + outParamsStr; return location; } /** * Tells whether a parameter should not be reported to the redirection * address. * * @param paramName * @return */ protected boolean isParameterToSkip(String paramName) { String[] paramsToSkip = { MsgId.PARAM_LEAVING_FORM.getText() }; for (String param : paramsToSkip) { if (param.equals(paramName)) { return true; } } return false; } /** * @param buffer * @param first * @param param */ protected boolean addParam(StringBuffer buffer, boolean first, String paramName, String paramValue) { if (first == false) { buffer.append('&'); } buffer.append(paramName + "=" + paramValue); return false; } /** * Builds the appropriate target URL to the given form. Essentially, sets * the target on the same * context. * * @param nextFormName * @param initParams * @param lsuffix * @return */ protected String buildNextFormUrl(String nextFormName, Map<String, String> initParams, String lsuffix, boolean autoAdvance) { // StringBuffer outParamsStr = new StringBuffer(256); // outParamsStr.append(MsgId.PARAM_DATA_TYPE.getText()); // outParamsStr.append('='); // outParamsStr.append(nextFormName); // outParamsStr.append('&'); // outParamsStr.append(MsgId.PARAM_FORM_TYPE.getText()); // outParamsStr.append('='); // outParamsStr.append(MsgId.INT_FORMTYPE_WKFLW); // String location = getServletURL() + "?" + outParamsStr; String location = buildWorkflowFormURL(nextFormName); return addRedirectionParameters(location, autoAdvance, true, initParams, lsuffix); } /** * Redirects the XForms engine to a user-chosen URL, providing (unless told * otherwise) some info * about the workflow task that has just been completed. * * @param formName * the name of the form being left * @param nextPage * the target URL given as a URL parameter * @param URLsuffix * @param noAddInfo * whether info parameters should be (if set to false) added to * the URL. */ protected void redirectToClientURL(String formName, String nextPage, String URLsuffix, boolean noAddInfo) { String location = nextPage; String infixe = (nextPage.indexOf('?') == -1) ? "?" : "&"; if (noAddInfo == false) { location += infixe + URLsuffix + "&" + MsgId.PARAM_LEAVING_FORM + "=" + formName; } super.redirectClient(location); } /** * Deals with redirecting the client in case the transition failed. * * @param currentPage * @param URLsuffix */ protected void redirectFailure(Page currentPage, String URLsuffix) { Map<String, String> initParams = currentPage.getInitParams(); String nextPage = null; // // determine the redirection page's URL nextPage = initParams.get(MsgId.PARAM_FAILURE_PAGE.getText()); if (nextPage == null) { // #1656 // there may be something specified in the Xtension property of this form nextPage = controller.getXtensionFailurePage(currentPage.getFormName()); } if (nextPage != null) { // // determine whether to append the suffix boolean foundInParams = initParams.containsKey(MsgId.PARAM_SKIP_ADDITIONAL_INFO.getText()); boolean skipInfo = false; if (foundInParams) { skipInfo = (StringUtils.equals(initParams.get(MsgId.PARAM_SKIP_ADDITIONAL_INFO.getText()), "true")); } else { // #1656 // there may be something specified in the Xtension property of this form skipInfo = controller.getXtensionSkipAdditionalInfo(currentPage.getFormName(), currentPage.getFormType()); } // // redirectToClientURL(currentPage.getFormName(), nextPage, URLsuffix, skipInfo); return; } PageInfoBean bean = new PageInfoBean(currentPage); navigationPath.setCurrentPage(bean); setSubmissionDefaultLocation(getServletURL(), result); } /** * Sets the current user name from the URL parameters. * * @return */ protected String getCurrentUserName() { userName = currentPage.getInitParams().get(MsgId.PARAM_USER_NAME.getText()); return (userName != null) ? userName : controller.getParamUserName(transaction.getInitParams()); } /** * Finds the workflow definition id for a form. * * @param candidateProcessId * the url param the form was called with (if relevant). null if * no URL param was * given. * @param formName * the name of the workflow form, e.g. * "DigitizationProcess_Debut" * @return candidateProcessId if given. Otherwise, the id for the latest * deployed version of the * process definition */ protected String findProcessId(String candidateProcessId, String formName) { if (StringUtils.trimToNull(candidateProcessId) != null) { return candidateProcessId; } logger.debug("Entering findProcessId"); String processName = controller.workflowExtractProcessNameFromFormName(formName); logger.debug(" got processName: " + processName); String definitionName = controller.getWorkflowBlueXMLDefinitionName(processName); logger.debug(" got definitionName: " + definitionName); return controller.workflowGetIdForProcessDefinition(transaction, definitionName); // $$ } /** * Transforms form fields values into properties of the current task. * * @param properties * @param node * @param taskBean * @param processId * @return null in case of exception * @throws ServletException */ protected GenericClass collectTaskProperties(HashMap<QName, Serializable> properties, Node node, WorkflowTaskInfoBean taskBean, String processId) throws ServletException { String taskTypeName = taskBean.getFormName(); String taskTypeId = taskBean.getTaskId(); Element root; if (node instanceof Document) { root = ((Document) node).getDocumentElement(); } else { root = (Element) node; } // Element processElt = DOMUtil.getChild(root, processName); Element taskElt = DOMUtil.getChild(root, taskTypeName); GenericClass toCreate; // Assemble a BlueXML Class.xsd object from the instance, dealing with FileFields (#1209) PersistFormResultBean resultBean = controller.persistWorkflow(transaction, taskTypeName, taskElt, currentPage.getInitParams()); toCreate = resultBean.getResultClass(); // add task properties built from the attributes derived from form fields String processName = controller.workflowExtractProcessNameFromFormName(taskTypeName); String namespaceURI = controller.getWorkflowNamespaceURI(processName); List<GenericAttribute> attributes = toCreate.getAttributes().getAttribute(); for (GenericAttribute attribute : attributes) { String localName = attribute.getQualifiedName(); QName qname = QName.createQName(namespaceURI, localName); List<String> propDefs = controller.workflowGetTaskPropertiesQNames(transaction, processId, taskTypeId); // we need to filter out properties that do not belong with this task if (propDefs.contains(qname.toString())) { Serializable value; List<ValueType> allValues = attribute.getValue(); if (allValues.size() > 0) { if (allValues.size() == 1) { value = attribute.getValue().get(0).getValue(); } else { value = (Serializable) allValues; } properties.put(qname, value); } } } return toCreate; } /** * Tells whether the current user is a legitimate actor for this task. * * @param taskBean * @param properties * @return true if the user is the actorId or belongs to the pooled actors */ protected boolean validateCurrentUser(WorkflowTaskInfoBean taskBean, HashMap<QName, Serializable> properties) { String actorIds = taskBean.getActorId(); if (actorIds != null) { // #1514: support for multiple groups/users via comma-separated list String[] actors = StringUtils.split(actorIds, ","); for (String anActor : actors) { anActor = resolveActorId(StringUtils.trim(anActor), properties); if (anActor.equals("initiator")) { // #1531 return true; } if (anActor.startsWith("#{")) { // oups Xform can't interpret this variable we hope that current user can execute this transition return true; } if (StringUtils.equals(anActor, userName)) { return true; } } } String pooledActors = taskBean.getPooledActors(); if (pooledActors == null) { return false; } String[] actors = StringUtils.split(pooledActors, ","); for (String anActor : actors) { anActor = StringUtils.trim(anActor); // search the registered task groups amongst the user's groups Set<String> userGroups = controller.systemGetContainingGroups(transaction, userName); if (userGroups != null) { // <-- can this ever not be so ? String authorizedGroup = PermissionService.GROUP_PREFIX + anActor; for (String aUserGroup : userGroups) { if (StringUtils.equals(aUserGroup, authorizedGroup)) { return true; } } } } return false; } /** * Resolves the actorId using the indirection format supported by Alfresco * in workflow * definitions. * * @param actorId * e.g. "#{wfbxifremer_user1}" * @param properties * @return */ protected String resolveActorId(String actorId, HashMap<QName, Serializable> properties) { // #1532: support for expressions in actorId/pooledActors // int pos = actorId.indexOf("#{"); if (pos != 0) { return actorId; } pos += 2; int posEnd = actorId.indexOf("}", pos); if (posEnd == -1) { return actorId; } String expr = actorId.substring(pos, posEnd); pos = expr.indexOf('_'); if (pos == -1) { return actorId; } String property = expr.substring(pos + 1); for (QName qname : properties.keySet()) { if (qname.getLocalName().endsWith(property)) { Serializable serializable = properties.get(qname); return serializable.toString(); } } return actorId; } /** * Finds which of the given tasks matches the given task name. * * @param formTaskName * the task name, inferred from a form name * @param taskNames * a list of active tasks * @return the relevant task for the form */ protected String findRelevantTaskForForm(String formTaskName, List<String> taskNames) { if (taskNames == null) { return null; } for (String taskInfoString : taskNames) { logger.debug(">>Testing match against task: " + taskInfoString); String taskName = getNameFromTaskIdNameTitle(taskInfoString); if (StringUtils.equals(formTaskName, taskName)) { logger.debug(" >> Match found for task: " + taskInfoString); return taskInfoString; } } return null; } /** * Gets the name from a task information string as formatted by * {@link XFormsWork.wfGetCurrentTasks}. * * @param taskInfoString * @return the name of the task, e.g. "wfbxDigitizationProcess:Debut" */ protected String getNameFromTaskIdNameTitle(String taskInfoString) { // #1534 int pos = taskInfoString.indexOf(TASK_SEPARATOR); int posEnd = taskInfoString.indexOf(TASK_SEPARATOR, pos + TASK_SEPARATOR_LENGTH); String result = taskInfoString.substring(pos + TASK_SEPARATOR_LENGTH, posEnd); return result; } /** * Gets the id from a task information string as formatted by * {@link XFormsWork.wfGetCurrentTasks}. * * @param taskInfoString * @return the id of the task, e.g. "jbpm$64". */ protected String getIdFromTaskIdNameTitle(String taskInfoString) { // #1534 int pos = taskInfoString.indexOf(TASK_SEPARATOR); String result = taskInfoString.substring(0, pos); return result; } protected class TransitionResultBean { protected boolean success; protected List<String> tasks; public TransitionResultBean() { super(); this.success = false; this.tasks = null; } /** * @return the success */ public boolean isSuccess() { return success; } /** * @return the tasks */ public List<String> getTasks() { return tasks; } /** * @param success * the success to set */ public void setSuccess(boolean success) { this.success = success; } /** * @param tasks * the tasks to set */ public void setTasks(List<String> tasks) { this.tasks = tasks; } } }