Work Packages

Document created by resplin Employee on Jun 6, 2015Last modified by alfresco-archivist on Aug 31, 2016
Version 4Show Document
  • View in full screen mode

Obsolete Pages{{Obsolete}}

The official documentation is at: http://docs.alfresco.com



DRAFT DRAFT DRAFT

This page describes some ideas around Work Package capabilities. Note that the content of this page relates to current thinking - it does not describe what is already in the product, and does not constitute any commitment from Alfresco to deliver what is described. This page is being used (or has been used) by the engineers at Alfresco to record and share their thoughts on proposed Work Package capabilities. The details are subject to change.


Overview


Work Packages will provide the ability to manage collections of content assets as a single entity, enabling nodes that are created, updated, and (importantly) deleted to be automatically recorded against a work package and then subjected to workflow and transferred as a single entity. Within the Project 'Swift' timeframe, the Work Package features will focus on workflow-driven content production i.e. create a work package, change some assets in the scope of that work package, and then run a workflow over the contents of the work package. For Swift the appropriate initial Work Package API and Share-based UI is required. Work Packages will form a key concept of the WCM editorial experience allowing collaboration and management around assets.


Summary


In this initial version, a work package is a named entity that has a collection of nodes that have been either created, updated or deleted within the work package. There is a corresponding work package service that provides an appropriate abstraction. Operations on this service include:


  • createWorkPackage
  • renameWorkPackage
  • listWorkPackages
  • setCurrentWorkPackage
  • unsetCurrentWorkPackage
  • listNodes
  • deleteWorkPackage

Before carrying out any operation that one wants to be recorded against a work package, a call must be made to setCurrentWorkPackage. This records the context of the operation, and nodes that are affected by the operation are 'recorded' against the work package. This occurs synchronously at the point the operation in question has been committed. At the end of the operation, unsetCurrentWorkPackage must be called. Recorded changes include nodes that have been either created, updated (including property updates and/or content updates) or deleted.

A work package is a node in the repository. Work packages may be 'categorised' into a hierarchical structure ('work package directory') of work package categories. A work package or work package category must be uniquely named within its parent work package category. The hierarchy starts with a root work package category. Note: the actual node operations that manage the work package hierarchy (including changes to the hierarchy nodes themselves, such as a rename) are not 'recorded', even if setCurrentWorkPackage has been called.

Workp_slide1.png


Example scenarios


Web Producer UI wireframes


Workp_slide2.png
Workp_slide3.png


File System Protocols


In the case of CIFS and FTP, it is proposed that the root of the work package hierarchy could be exposed as a shared drive named 'WorkPackages' (by default).

Each work package is associated to one (or possibly more) folder(s) in the repository such that those folders are mapped (using an association) into the path space below the work package.

For example, if I create a work package 'X' within the scope of site 'A' in Share, then perhaps the corresponding work package node will be placed at '/Company Home/Data Dictionary/Work Packages/Sites/A/X'. By default, we might map (with an association) the site's document library into the work package, so that in effect the document library gains an additional implicit path: '/Company Home/Data Dictionary/Work Packages/Sites/A/X/docLib'.

Now, through CIFS and FTP we could enable navigation via the path '/WorkPackages/Sites/A/X/contentLibrary'. Any change that occurs below this path (create, update, delete of file or folder) is automatically recorded against work package 'X'.

In the case of WebDAV, we could either consider exposing new top-level directories (such as '/Alfresco' and '/WorkPackages') or otherwise it should be possible to navigate to a path such as '/Data Dictionary/Work Packages/Sites/A/X/docLib'. Again, any change that occurs below this path (create, update, delete of file or folder) is automatically recorded against work package 'X'.


Work Package Service (Java API)


API operations (summary)


Work Package


  • createWorkPackage - create a named 'WP' (note: uniquely named within a specified WP category, including root WP category)
  • getWorkPackage - get a named WP (within a specified WP category) or get WP from it's ref
  • resolveWorkPackageRefFromPath - resolve WP from a path relative to the root WP category
  • renameWorkPackage - rename WP (note: uniquely named within a specified WP category)
  • deleteWorkPackage - delete WP
  • listWorkPackages - list WPs (within a specified WP category)
  • findAllWorkPackages - find all WPs across WP category hierarchy (note: can return duplicate WP names)
  • findWorkPackages - find WPs based on name filter (note: can return duplicate WP names) - either across WP category hierarchy or within WP category (optionally include sub-categories)
  • setCurrentWorkPackage - setting current WP will cause subsequent node changes to be (automatically) recorded (note: any previously set WP will be unset before setting new WP)
  • unsetCurrentWorkPackage - unsetting current WP will stop subsequent node changes to be (automatically) recorded
  • listNodes - list visible node changes recorded within a WP
  • listWorkPackagesForNode - list WPs in which the given node has been 'recorded' (or empty list if not recorded by any WP)
  • addNode - manually record node change for a WP (note: changes will be automatically recorded for currently set WP)
  • removeNode - remove node change from WP
  • removeAllNodes - remove all visible node changes from WP
  • getWorkPackageHelper - get WP helper to 'runIn' scope of WP (wraps setCurrentWorkPackage and unsetCurrentWorkPackage)
  • startWorkflow - start workflow based on the given WP - by creating and associating a system WFP (WorkFlow Package) prior to starting the workflow

Work Package Category


  • getRootWorkPackageCategory - get root WP category
  • createWorkPackageCategory - create a named WP category (note: uniquely named within a specified parent WP category)
  • getWorkPackageCategory - get a named WP category (within a specified parent WP category) or get WP category from it's ref
  • renameWorkPackageCategory - rename WP category (note: uniquely named within a specified parent WP category, also the root WP category can only be renamed by an admin)
  • deleteWorkPackageCategory - delete WP category (note: this will cascade delete any WP categories or WPs below this parent WP category, also the root WP category cannot be deleted)
  • listWorkPackageCategories - list WP categories (within a specified WP category)
  • findAllWorkPackageCategories - find all WP categories across WP category hierarchy (note: can return duplicate WP category names)
  • recategoriseWorkPackage - recategorise WP by effectively moving it to another WP category (note: as long as it is uniquely named in the new destination)
  • recategoriseWorkPackageCategory - recategorise wWP category by effectively moving it to another parent WP category (note: as long as it is uniquely named in the new destination)

Java interfaces (example)


WorkPackageHelper



/**
* Work Package Helper interface
*
* @author janv
*/
public interface WorkPackageHelper
{
    /**
     * Method containing the work to be done in the scope of the work package.
     *
     * @return Return the result of the operation
     */
    public interface RunInWork<Result>
    {
        Result doWork();
    }
   
    /**
     * runIn scope of given work package
     *
     * @param <R>
     * @param wpRef
     * @param runWork
     * @return
     */
    public <R> R runIn(NodeRef wpRef, RunInWork<R> runWork);
}

WorkPackageService



/**
* Work Package Service interface
*
* The work package service is responsible for recording node changes (created, updated or deleted) within the context of the currently set work package. The work package
* effectively maintains an unordered set of references to nodes.
*
* Note: in the case of deleted nodes, the nodes do not actually exist hence the references are to deleted ghosts which maintain minimal context, including name and
* read permissions.
*
* @author janv
*/
@PublicService
public interface WorkPackageService
{
    /**
     * Get root work package category
     *
     * @return WorkPackageCategoryInfo
     */
    @NotAuditable
    public WorkPackageCategory getRootWorkPackageCategory();
   
    /**
     * Create an empty named work package category
     *
     * The work package category name must be unique within the context of the parent work package category
     *
     * @param parentWpCategoryRef  parent work package category ref (required)
     * @param categoryName  category name (required)
     * @return WorkPackageCategoryInfo
     * @throws InvalidNodeRefException, DuplicateChildNodeNameException
     */
    @Auditable(parameters={'parentWpCategoryRef, categoryName'})
    public WorkPackageCategory createWorkPackageCategory(NodeRef parentWpCategoryRef, String categoryName) throws InvalidNodeRefException, DuplicateChildNodeNameException;
   
    /**
     * Get work package category by its unique ref
     *
     * @param wpCategoryRef  work package category ref (required)
     * @return WorkPackageCategoryInfo (or null if not found)
     */
    @NotAuditable
    public WorkPackageCategory getWorkPackageCategory(NodeRef wpCategoryRef);
   
    /**
     * Get work package category by its name (within given work package category)
     *
     * @param parentWpCategoryRef  work package category ref (required)
     * @param categoryName  category name (required)
     * @return WorkPackageCategoryInfo (or null if not found)
     */
    @NotAuditable
    public WorkPackageCategory getWorkPackageCategory(NodeRef parentWpCategoryRef, String categoryName);
   
    /**
     * List work package categories within parent category
     *
     * @param parentWpCategoryRef  can be null for root (of work package category directory)
     * @return
     * @throws InvalidNodeRefException
     */
    @NotAuditable
    public List<WorkPackageCategory> listWorkPackageCategories(NodeRef parentWpCategoryRef) throws InvalidNodeRefException;
   
    /**
     * Find all work package categories
     *
     * @return List<WorkPackageCategory>
     */
    @NotAuditable
    public List<WorkPackageCategory> findAllWorkPackageCategories();
   
    /**
     * Delete work package category
     *
     * This will also cascade delete (work package categories and work packages below this category)
     *
     * @param wpCategoryRef  work package category (required)
     * @throws InvalidNodeRefException
     */
    @Auditable(parameters={'wpCategoryRef'})
    public void deleteWorkPackageCategory(NodeRef wpCategoryRef) throws InvalidNodeRefException, AccessDeniedException;
   
    /**
     * Rename work package category
     *
     * @param wpCategoryRef  work package category (required)
     * @param newCatName  new category name (required)
     * @throws InvalidNodeRefException, DuplicateChildNodeNameException
     */
    @Auditable(parameters={'wpCategoryRef, newCatName'})
    public void renameWorkPackageCategory(NodeRef wpCategoryRef, String newCatName) throws InvalidNodeRefException, DuplicateChildNodeNameException;
   
    /**
     * Recategorise work package under a different work package category
     *
     * @param wpRef  work package (required)
     * @param newWpCategoryRef  (new) work package category - must already exist (required)
     * @throws InvalidNodeRefException, DuplicateChildNodeNameException
     */
    @Auditable(parameters={'wpRef, newWpCategoryRef'})
    public void recategoriseWorkPackage(NodeRef wpRef, NodeRef newWpCategoryRef) throws InvalidNodeRefException, DuplicateChildNodeNameException, AccessDeniedException;
   
    /**
     * Create an empty named work package
     *
     * The work package name must be unique within the context of the given work package category
     *
     * @param wpCategoryRef  work package category (required)
     * @param wpName  work package name (required)
     * @return WorkPackageInfo
     * @throws InvalidNodeRefException, DuplicateChildNodeNameException
     */
    @Auditable(parameters={'wpCategoryRef, wpName'})
    public WorkPackage createWorkPackage(NodeRef wpCategoryRef, String wpName) throws InvalidNodeRefException, DuplicateChildNodeNameException;
   

   
    /**
     * Recategorise work package category under a different work package category
     *
     * @param wpCategoryRef  work package category (required)
     * @param newWpCategoryRef  (new) work package category - must already exist, can be null for root (of work package category directory)
     * @throws InvalidNodeRefException, DuplicateChildNodeNameException
     */
    @Auditable(parameters={'wpCategoryRef, newWpCategoryRef'})
    public void recategoriseWorkPackageCategory(final NodeRef wpCategoryRef, final NodeRef newWpCategoryRef) throws InvalidNodeRefException, DuplicateChildNodeNameException;
   
    /**
     * Get work package info by its unique ref
     *
     * @param wpRef  work package (required)
     * @return WorkPackageInfo (or null if not found)
     * @throws InvalidNodeRefException (if wpRef does not exist or is of the wrong type)
     */
    @NotAuditable
    public WorkPackage getWorkPackage(NodeRef wpRef) throws InvalidNodeRefException;
   
    /**
     * Get work package info by its name (within given work package category)
     *
     * @param wpCategoryRef  work package category
     * @param wpName  work package name
     * @return WorkPackageInfo (or null if not found)
     * @throws InvalidNodeRefException (if wpCategoryRef does not exist)
     */
    @NotAuditable
    public WorkPackage getWorkPackage(NodeRef wpCategoryRef, String wpName) throws InvalidNodeRefException;
   
    /**
     * Resolve work package (if any) from given path elements that are relative to root work package category
     *
     * @param pathElements
     * @return NodeRef work package ref + any remaining path elements (beyond the work package)
     */
    @NotAuditable
    public Pair<String>> resolveWorkPackageRefFromPath(List<String> pathElements);
   
    /**
     * Rename the work package
     *
     * @param wpRef  work package (required)
     * @param newWpName  new work package name (required)
     * @throws InvalidNodeRefException, DuplicateChildNodeNameException
     */
    @Auditable(parameters={'wpRef, newWpName'})
    public void renameWorkPackage(NodeRef wpRef, String newWpName) throws InvalidNodeRefException, DuplicateChildNodeNameException;
   
    /**
     * Delete work package and its collection of references
     *
     * Note: this does not delete the referenced nodes themselves
     *
     * @param wpRef  work package (required)
     * @throws InvalidNodeRefException (if wpRef does not exist)
     */
    @Auditable(parameters={'wpRef'})
    public void deleteWorkPackage(NodeRef wpRef) throws InvalidNodeRefException;
   
    /**
     * List of work package info contained within the given work package category
     *
     * @param wpCategoryRef  work package category
     * @return List<WorkPackageInfo> (or empty list if there are no visible work packages)
     * @throws InvalidNodeRefException
     */
    @NotAuditable
    public List<WorkPackage> listWorkPackages(NodeRef wpCategoryRef) throws InvalidNodeRefException;
   
    /**
     * Find all work packages
     *
     * @return List<WorkPackage>
     */
    @NotAuditable
    public List<WorkPackage> findAllWorkPackages();
   
    /**
     * Find work packages that match a given name filter (such that the nameFilter is a substring of the work package name)
     *
     * Note: duplicate work package names may appear across work package categories.
     *
     * @param wpNameFilter work package name filter (match this as a substring, no wild cards required - empty string matches all)
     * @return List<WorkPackage>
     */
    @NotAuditable
    public List<WorkPackage> findWorkPackages(String wpNameFilter);
   
    /**
     * Find work packages within a parent category that match a given name filter (such that the nameFilter is a substring of the work package name)
     *
     * The search can be deep (ie. include sub categories) or shallow (ie. do not include sub categories).
     *
     * Passing an empty name filter will match all work packages within the specified context, for example:
     * - a shallow search with empty name filter will match all work packages within the parent work package category (equivalent to @listWorkPackages)
     * - a deep search with empty name filter will match all work packages below and including the specified work package category
     * - a deep search with empty name filter from the root work package category will match all work packages (equivalent to @findAllWorkPackages)
     *
     * Note: in the case of a deep search, duplicate work package names may appear across the work package categories.
     *
     * @param wpCategoryRef work package category
     * @param wpNameFilter work package name filter (match this as a substring, no wild cards required - empty string matches all)
     * @param includeSubCategories if false then perform a shallow search else if true then perform a deep search
     * @return List<WorkPackage>
     */
    @NotAuditable
    public List<WorkPackage> findWorkPackages(NodeRef wpCategoryRef, String wpNameFilter, boolean includeSubCategories);
   
    /**
     * Explicitly add node reference to the work package
     *
     * If the node has already been recorded against this this work package (either manually or automatically) then the existing change will remain as-is (created, updated or
     * deleted). Otherwise, the node must exist and it will be recorded with change type 'updated'. If the node does not exist then an exception is thrown.
     *
     * Note: This implies that 'deleted' node changes can only be recorded automatically.
     *
     * @param wpRef
     * @param nodeRef
     * @throws InvalidNodeRefException (if wpRef does not exist or if the nodeRef does not exist)
     */
    @Auditable(parameters={'wpRef, nodeRef'})
    public void addNode(NodeRef wpRef, NodeRef nodeRef) throws InvalidNodeRefException;
   
    /**
     * Remove node reference from the work package
     *
     * @param wpRef  work package (required)
     * @param nodeRef
     * @throws InvalidNodeRefException (if wpRef does not exist)
     */
    @Auditable(parameters={'wpRef, nodeRef'})
    public void removeNode(NodeRef wpRef, NodeRef nodeRef) throws InvalidNodeRefException;
   
    /**
     * Remove all nodes from work package
     *
     * @param wpRef  work package (required)
     * @return WorkPackageChange
     * @throws InvalidNodeRefException
     */
    @Auditable(parameters={'wpRef'})
    public void removeAllNodes(NodeRef wpRef) throws InvalidNodeRefException;
   
    /**
     * List all changes referenced by the work package
     *
     * @param wpRef  work package (required)
     * @return Set<WorkPackageChange>
     * @throws InvalidNodeRefException
     */
    @NotAuditable
    public Set<WorkPackageChange> listNodes(NodeRef wpRef) throws InvalidNodeRefException;
   
    /**
     * List changes referenced by the work package for a given change type
     *
     * @param wpRef  work package (required)
     * @param changeType  change type (required)
     * @return
     * @throws InvalidNodeRefException
     */
    @NotAuditable
    public Set<WorkPackageChange> listNodes(NodeRef wpRef, ChangeType changeType) throws InvalidNodeRefException;
   
    /**
     * List work packages in which the given node has been 'recorded' or empty list (if not recorded by any work package).
     *
     * Note:
     * - a node can be recorded as CREATED in zero or ONE work package (ie. it cannot be appear as CREATED in more than one work package)
     * - a node can be recorded as UPDATED in zero or more work packages (and it can also appear as CREATED in another work package)
     * - a node can be recorded as DELETED in zero or ONE work package (ie. it cannot be appear as DELETED in more than one work package) and more
     *   over it will no longer appear as CREATED or UPDATED in any other work packages
     *
     * @param nodeRef
     * @return List<WorkPackage>
     * @throws InvalidNodeRefException
     */
    @NotAuditable
    public List<WorkPackage> listWorkPackagesForNode(NodeRef nodeRef) throws InvalidNodeRefException;
   
    /**
     * Get helper to run work in the scope of a given work package (ie. wraps setCurrentWorkPackage / unsetCurrentWorkPackage)
     *
     */
    @NotAuditable
    public WorkPackageHelper getWorkPackageHelper();
   
    /**
     * Start workflow for the given work package
     *
     * @param wpRef  work package (required)
     * @param workflowDefinitionId
     * @param parameters
     */
    @Auditable(parameters={'wpRef', 'workflowDefinitionId', 'parameters'})
    public WorkflowPath startWorkflow(NodeRef wpRef, String workflowDefinitionId, Map<QName, Serializable> parameters);
   
    /**
     * Set current work package (for the current thread)
     *
     * @param wpRef  work package (required)
     */
    @Auditable(parameters={'wpRef'})
    public void setCurrentWorkPackage(NodeRef wpRef);
   
    /**
     * Get current work package or null if no current work package is set (for the current thread)
     *
     * @return
     */
    @NotAuditable
    public NodeRef getCurrentWorkPackage();
   
    /**
     * Unset current work package (for the current thread)
     *
     * @return
     */
    @Auditable(parameters={})
    public void unsetCurrentWorkPackage();
   
    /**
     * Add folder location to work package (can be used for path-based navigation / scoping)
     *
     * @param wpRef  work package (required)
     * @param folderRef  folder ref (required)
     * @throws InvalidNodeRefException
     */
    @Auditable(parameters={'wpRef', 'folderRef'})
    public void addFolderLocation(NodeRef wpRef, NodeRef folderRef) throws InvalidNodeRefException;
   
    /**
     * Remove folder location from work package
     *
     * @param wpRef  work package (required)
     * @param folderRef  folder ref (required)
     * @throws InvalidNodeRefException
     */
    @Auditable(parameters={'wpRef', 'folderRef'})
    public void removeFolderLocation(NodeRef wpRef, NodeRef folderRef) throws InvalidNodeRefException;
   
    /**
     * List zero or more folder locations on work package
     *
     * @param wpRef
     * @return list of folder locations set on work package (or empty list if none)
     * @throws InvalidNodeRefException
     */
    @NotAuditable
    public List<NodeRef> listFolderLocations(NodeRef wpRef) throws InvalidNodeRefException;
}


WorkPackage



public interface WorkPackage
{
    public String getName();
   
    public NodeRef getRef();
   
    public NodeRef getParentCategoryRef();
}

WorkPackageChange



public interface WorkPackageChange
{
    public enum ChangeType { CREATED, UPDATED, DELETED }
   
    public NodeRef getWorkPackageRef();
   
    public NodeRef getNodeRef(); // live (for created or updated) or ghost (for deleted)
   
    public ChangeType getChangeType();
   
    public NodeRef getOriginalNodeRef(); // original live (if deleted ghost)
}

WorkPackageCategory



public interface WorkPackageCategory
{
    public String getName();
   
    public NodeRef getRef();
   
    public NodeRef getParentCategoryRef();
}

Implementation Notes


Data Model



<model name='wp:workPackageModel' xmlns='http://www.alfresco.org/model/dictionary/1.0'>

    <description>Alfresco Work Package Model</description>
    <author>janv</author>
    <published>2010-07-26</published>
    <version>0.2</version>
   
    <imports>
        <import uri='http://www.alfresco.org/model/dictionary/1.0' prefix='d'/>
        <import uri='http://www.alfresco.org/model/content/1.0' prefix='cm'/>
        <import uri='http://www.alfresco.org/model/bpm/1.0' prefix='bpm'/>
    </imports>
   
    <namespaces>
        <namespace uri='http://www.alfresco.org/model/workpackage/1.0' prefix='wp'/>
    </namespaces>
   
    <types>
        <type name='wp:workPackageCategory'>
            <title>Work Package Category</title>
            <description>Work Package Category</description>
           
            <parent>cm:folder</parent>
           
            <archive>false</archive>
        </type>
       
        <type name='wp:workPackage'>
            <title>Work Package</title>
            <description>Work Package</description>
           
            <parent>cm:folder</parent>
           
            <archive>false</archive>
           
            <associations>
                <association name='wp:sysWorkflowPackage'>
                    <source>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </source>
                    <target>
                        <class>bpm:package</class>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </target>
                </association>
                <association name='wp:folderLocation'>
                    <source>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </source>
                    <target>
                        <class>cm:folder</class>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </target>
                </association>
                <association name='wp:created'>
                    <source>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </source>
                    <target>
                        <class>cm:cmobject</class>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </target>
                </association>
                <association name='wp:updated'>
                    <source>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </source>
                    <target>
                        <class>cm:cmobject</class>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </target>
                </association>
                <association name='wp:deleted'>
                    <source>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </source>
                    <target>
                        <class>cm:cmobject</class>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </target>
                </association>
            </associations>
        </type>
       
        <type name='wp:deletedGhost'>
            <title>Deleted Ghost</title>
            <description>Deleted Ghost</description>
            <parent>cm:cmobject</parent>
           
            <archive>false</archive>
           
            <properties>
                <property name='wp:deletedNodeRef'>
                    <type>d:text</type>
                    <mandatory>true</mandatory>
                </property>
                <property name='wp:deletedOriginalParentAssoc'>
                   <type>d:childassocref</type>
                   <mandatory>true</mandatory>
                </property>
            </properties>
        </type>
       
        <type name='wp:deletedGhostsRoot'>
            <title>Work Package Deleted Ghosts Root</title>
            <description>Node type used for root of (ultimately temporary) storage of permanently deleted nodes</description>
            <parent>cm:content</parent>
           
            <archive>false</archive>
           
            <associations>
                <child-association name='wp:deletedGhost'>
                    <source>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </source>
                    <target>
                        <class>wp:deletedGhost</class>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </target>
                </child-association>
            </associations>
        </type>
       
    </types>
   
</model>

Engineering Notes

Attachments

Outcomes