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.
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.
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:
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_slide2.png
Workp_slide3.png
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 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);
}
/**
* 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;
}
public interface WorkPackage
{
public String getName();
public NodeRef getRef();
public NodeRef getParentCategoryRef();
}
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)
}
public interface WorkPackageCategory
{
public String getName();
public NodeRef getRef();
public NodeRef getParentCategoryRef();
}
<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>