/******************************************************************************* * Imixs Workflow * Copyright (C) 2001, 2011 Imixs Software Solutions GmbH, * http://www.imixs.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 2 * 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 can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html * * Project: * http://www.imixs.org * http://java.net/projects/imixs-workflow * * Contributors: * Imixs Software Solutions GmbH - initial API and implementation * Ralph Soika - Software Developer *******************************************************************************/ package org.imixs.marty.plugins; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Vector; import java.util.logging.Logger; import org.imixs.workflow.ItemCollection; import org.imixs.workflow.WorkflowContext; import org.imixs.workflow.WorkflowKernel; import org.imixs.workflow.engine.plugins.AbstractPlugin; import org.imixs.workflow.engine.plugins.ResultPlugin; import org.imixs.workflow.exceptions.PluginException; import org.imixs.workflow.exceptions.QueryException; /** * The Marty TeamPlugin organizes the hierarchical order of a workitem between * processes, spaces and workitems. A WorkItem is typically assigned to a * process and a optional to one ore more space entities. These references are * stored in the $UniqueIDRef property of the WorkItem. In addition to the * $UniqueIDRef property the TeamPlugin manages the properties txtProcessRef and * txtSpaceRef which containing only uniqueIDs of the corresponding entity type. * The properties txtProcessRef and txtSpaceRef can be modified by an * application to reassign the workitem. * * This plug-in supports also additional workflow properties for further * processing. The method computes the team members and the name of the assigned * process and space. * * <p> * The TeamPlugin updates the following properties: * <ul> * <li>namSpaceTeam * <li>namSpaceManager * <li>namSpaceAssist * <li>namSpaceName * <li>txtSpaceRef * <li>namProcessTeam * <li>namProcessManager * <li>namProcessAssist * <li>txtProcessName * <li>txtProcessRef * * The name properties are used in security and mail plug-ins. * * The properties 'txtProcessRef' and 'txtSpaceRef' are optional and can provide * the current $uniqueIDs for referenced space or process entities. The Plug-in * updates the $UniqueIDRef property if these properties are filled. * * If the workItem is a child to another workItem (ChildWorkitem) the * information is fetched from the parent workItem. * * If the workflowresultmessage of the ActivityEntity contains a space or * process reference the plug-in will update the reference in the property * $uniqueIdRef. * * Example: * * <code> <item name="space">...</item> <item name="process">...</item> </code> * * The Plug-in should run before Access-, Application- and Mail-Plug-in. * * * Model: default * * @author rsoika * @version 2.0 */ public class TeamPlugin extends AbstractPlugin { public static final String INVALID_REFERENCE_ASSIGNED_BY_MODEL = "INVALID_REFERENCE_ASSIGNED_BY_MODEL"; public static final String NO_PROCESS_ASSIGNED = "NO_PROCESS_ASSIGNED"; private ItemCollection documentContext; private static Logger logger = Logger.getLogger(TeamPlugin.class.getName()); private Map<String, ItemCollection> entityCache = null; /** * Fetch workflowService and entityService from WorkflowContext */ @Override public void init(WorkflowContext actx) throws PluginException { super.init(actx); // init cache entityCache = new HashMap<String, ItemCollection>(); } /** * The method updates information from the Process and Space entiy * (optional) stored in the attribute '$uniqueIdref' * <ul> * <li>txtProcessRef * <li>txtSpaceRef * <li>namTeam * <li>namManager * <li>namProcessTeam * <li>namProcessManager * <li>txtSpaceName * <li>txtCoreProcessName * * If the workitem is a child to another workitem (ChildWorkitem) the * information is fetched from the parent workitem. * * If the workflowresultmessage contains a space entity reference the plugin * will update the reference in the property $uniqueIdRef. * * Example: * * <code> <item name="space">...</item> </code> * **/ @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public ItemCollection run(ItemCollection workItem, ItemCollection documentActivity) throws PluginException { documentContext = workItem; List<String> oldUnqiueIdRefList = workItem.getItemValue("$UniqueIdRef"); List<String> newUnqiueIDRefList = null; List<String> processRefList = null; List<String> spaceRefList = null; // 1.1) if txtProcessRef don't exists then search for process ids in // $UnqiueIDRef if (!workItem.hasItem("txtProcessRef") && !oldUnqiueIdRefList.isEmpty()) { processRefList = workItem.getItemValue("txtProcessRef"); for (String aUniqueID : oldUnqiueIdRefList) { ItemCollection entity = findEntity(aUniqueID); if (entity != null && "process".equals(entity.getItemValueString("type"))) { // update txtProcessRef processRefList.add(entity.getItemValueString(WorkflowKernel.UNIQUEID)); } } // update txtProcessRef workItem.replaceItemValue("txtProcessRef", processRefList); } else { // 1.1.1) validate content of txtProcessRef if (workItem.hasItem("txtProcessRef")) { processRefList = workItem.getItemValue("txtProcessRef"); List<String> verifiedRefList = new Vector<String>(); for (String aUniqueID : processRefList) { ItemCollection entity = findEntity(aUniqueID); // if the entity was not found by id we test if we can catch // it up by its name... if (entity == null) { entity = findRefByName(aUniqueID, "process"); if (entity != null) { String aID = entity.getItemValueString(WorkflowKernel.UNIQUEID); logger.info( "[TeamPlugin] processRefName '" + aUniqueID + "' translated into '" + aID + "'"); // verified aUniqueID = aID; } } if (entity != null && "process".equals(entity.getItemValueString("type"))) { // verified verifiedRefList.add(aUniqueID); } } // update txtProcessRef workItem.replaceItemValue("txtProcessRef", verifiedRefList); } } // 1.2) if txtSpaceRef don't exists then search for space ids in // $UnqiueIDRef if (!workItem.hasItem("txtSpaceRef") && !oldUnqiueIdRefList.isEmpty()) { spaceRefList = workItem.getItemValue("txtSpaceRef"); for (String aUniqueID : oldUnqiueIdRefList) { ItemCollection entity = findEntity(aUniqueID); if (entity != null && "space".equals(entity.getItemValueString("type"))) { // update txtProcessRef spaceRefList.add(entity.getItemValueString(WorkflowKernel.UNIQUEID)); } } // update txtProcessRef workItem.replaceItemValue("txtSpaceRef", spaceRefList); } else { // 1.2.1) validate content of txtSpaceRef if (workItem.hasItem("txtSpaceRef")) { processRefList = workItem.getItemValue("txtSpaceRef"); List<String> verifiedRefList = new Vector<String>(); for (String aUniqueID : processRefList) { ItemCollection entity = findEntity(aUniqueID); // if the entity was not found by id we test if we can catch // it up by its name... if (entity == null) { entity = findRefByName(aUniqueID, "space"); if (entity != null) { String aID = entity.getItemValueString(WorkflowKernel.UNIQUEID); logger.info("[TeamPlugin] spaceRefName '" + aUniqueID + "' translated into '" + aID + "'"); // verified aUniqueID = aID; } } if (entity != null && "space".equals(entity.getItemValueString("type"))) { // verified verifiedRefList.add(aUniqueID); } } // update txtProcessRef workItem.replaceItemValue("txtSpaceRef", verifiedRefList); } } /* * 2.) Check the txtActivityResult for a new Project reference. * * Pattern: * * '<item name="process">...</item>' '<item name="space">...</item>' */ ItemCollection evalItemCollection = ResultPlugin.evaluateWorkflowResult(documentActivity, workItem); if (evalItemCollection != null) { String sRef = fetchRefFromActivity("process", evalItemCollection); if (sRef != null && !sRef.isEmpty()) { logger.fine("[TeamPlugin] Updating process reference based on model information: " + sRef); workItem.replaceItemValue("txtProcessRef", sRef); } sRef = fetchRefFromActivity("space", evalItemCollection); if (sRef != null && !sRef.isEmpty()) { logger.fine("[TeamPlugin] Updating space reference based on model information: " + sRef); workItem.replaceItemValue("txtSpaceRef", sRef); } } // 3.) now synchronize txtProcessRef/txtSpaceRef with $UnqiueIDref processRefList = workItem.getItemValue("txtProcessRef"); spaceRefList = workItem.getItemValue("txtSpaceRef"); newUnqiueIDRefList = new Vector<String>(); newUnqiueIDRefList.addAll(processRefList); newUnqiueIDRefList.addAll(spaceRefList); for (String aUniqueID : oldUnqiueIdRefList) { ItemCollection entity = findEntity(aUniqueID); // check if this is a deprecated process ref if (entity != null && "process".equals(entity.getItemValueString("type")) && !processRefList.contains(aUniqueID)) { logger.fine("[TeamPlugin] remove deprecated processRef " + aUniqueID); } else { if (entity != null && "space".equals(entity.getItemValueString("type")) && !spaceRefList.contains(aUniqueID)) { logger.fine("[TeamPlugin] remove deprecated spaceRef " + aUniqueID); } else { // all other types of entities will still be contained... if (!newUnqiueIDRefList.contains(aUniqueID)) newUnqiueIDRefList.add(aUniqueID); } } } // 4.) finally we can now update the $UniqueIDRef property workItem.replaceItemValue("$UniqueIdRef", newUnqiueIDRefList); logger.fine("[TeamPlugin] Updated $UniqueIdRef: " + newUnqiueIDRefList); // and now $UnqiueIDref, txtProcessRef and txtSpaceRef are synchronized // and verified! // 6.) Now the team lists will be updated depending of the current // $uniqueidref List vSpaceTeam = new Vector(); List vSpaceManager = new Vector(); List vSpaceAssist = new Vector(); List vProcessTeam = new Vector(); List vProcessManager = new Vector(); List vProcessAssist = new Vector(); String sSpaceName = ""; String sProcessName = ""; // interate over all refs if defined for (String aUnqiueID : newUnqiueIDRefList) { ItemCollection entity = findEntity(aUnqiueID); if (entity != null) { String parentType = entity.getItemValueString("type"); // Test type property.... if ("process".equals(parentType)) { vProcessTeam.addAll(entity.getItemValue("namTeam")); vProcessManager.addAll(entity.getItemValue("namManager")); vProcessAssist.addAll(entity.getItemValue("namAssist")); sProcessName = entity.getItemValueString("txtname"); } if ("space".equals(parentType)) { vSpaceTeam.addAll(entity.getItemValue("namTeam")); vSpaceManager.addAll(entity.getItemValue("namManager")); vSpaceAssist.addAll(entity.getItemValue("namAssist")); sSpaceName = entity.getItemValueString("txtname"); } } } // update properties workItem.replaceItemValue("namSpaceTeam", vSpaceTeam); workItem.replaceItemValue("namSpaceManager", vSpaceManager); workItem.replaceItemValue("namSpaceAssist", vSpaceAssist); workItem.replaceItemValue("namProcessTeam", vProcessTeam); workItem.replaceItemValue("namProcessManager", vProcessManager); workItem.replaceItemValue("namProcessAssist", vProcessAssist); // removed duplicates... uniqueElements(workItem, "$UniqueIdRef"); uniqueElements(workItem, "txtProcessRef"); uniqueElements(workItem, "txtSpaceRef"); uniqueElements(workItem, "namSpaceTeam"); uniqueElements(workItem, "namSpaceManager"); uniqueElements(workItem, "namSpaceAssist"); uniqueElements(workItem, "namProcessTeam"); uniqueElements(workItem, "namProcessManager"); uniqueElements(workItem, "namProcessAssist"); logger.fine("[TeamPlugin] new ProcessName= " + sProcessName); logger.fine("[TeamPlugin] new SpaceName= " + sSpaceName); workItem.replaceItemValue("txtSpaceName", sSpaceName); workItem.replaceItemValue("txtProcessName", sProcessName); documentContext.removeItem("space"); documentContext.removeItem("process"); return documentContext; } /** * Helper method to lookup an entity in internal cache or load it from * database * * @param id * @return entity or null if not exits */ public ItemCollection findEntity(String id) { ItemCollection entity = entityCache.get(id); if (entity == null) { // load entity entity = this.getWorkflowService().getDocumentService().load(id); if (entity == null) { // add a dummy entry.... entity = new ItemCollection(); } // cache entity entityCache.put(id, entity); } // if entity is dummy return null if (entity.getItemValueString(WorkflowKernel.UNIQUEID).isEmpty()) return null; else return entity; } private String fetchRefFromActivity(String type, ItemCollection evalItemCollection) throws PluginException { String sRef = null; // Read workflow result directly from the activity definition String aActivityRefName = evalItemCollection.getItemValueString(type); // 1.) check if a new space reference is defined in the current // activity. This will overwrite the current value!! if (!"".equals(aActivityRefName)) { ItemCollection entity =this.getWorkflowService().getDocumentService().load(aActivityRefName); if (entity != null && !type.equals(entity.getItemValueString("type"))) { entity = null; } if (entity == null) { // load space entity entity = findRefByName(aActivityRefName, type); } if (entity != null) { sRef = entity.getItemValueString(WorkflowKernel.UNIQUEID); logger.fine("[TeamPlugin] found ref from Activity for: " + aActivityRefName); } else { // throw a PLuginException throw new PluginException(TeamPlugin.class.getSimpleName(), INVALID_REFERENCE_ASSIGNED_BY_MODEL, type + " '" + aActivityRefName + "' defined by the current model can not be found!"); } } return sRef; } /** * This method returns a Process or Space entity for a specified name. * Returns null if no entity with the provided name was found * * Because of the fact that spaces can be ordered in a hirachical order we * need to be a little more tricky if we seach for spaces.... */ private ItemCollection findRefByName(String aName, String type) { // String sQuery = "SELECT project FROM Entity AS project " + " JOIN project.textItems AS t2" // + " WHERE project.type = '" + type + "' " + " AND t2.itemName = 'txtname' " + " AND t2.itemValue = '" // + aName + "'"; if (type==null || aName==null) { return null; } String sQuery="(type:\"" + type + "\" AND txtname:\""+aName + "\")"; // because of the fact that spaces can be ordered in a hirachical order // we need to be a little more tricky if we seach for spaces.... // Important: to find ambigous space names we search for maxount=2! List<ItemCollection> col; try { col = this.getWorkflowService().getDocumentService().find(sQuery, 2, 0); if (col.size() == 0) { logger.warning("findRefByName '" + aName + "' not found!"); } else { if (col.size() > 1) { logger.warning("findRefByName '" + aName + "' is ambiguous!"); } else { // we found one! ItemCollection entity = col.iterator().next(); // update cache entityCache.put(entity.getItemValueString(WorkflowKernel.UNIQUEID), entity); return entity; } } } catch (QueryException e) { logger.warning("findRefByName - invalid query: " + e.getMessage()); } // no match return null; } /** * This method will remove empty or duplicate values from a list * * @param target * @param source * @return */ private void uniqueElements(ItemCollection entity, String field) { Vector<String> target = new Vector<String>(); @SuppressWarnings("unchecked") List<String> source = entity.getItemValue(field); for (String entry : source) { if (entry != null && !entry.isEmpty() && !target.contains(entry)) target.add(entry); } entity.replaceItemValue(field, target); } }