alfresco-community-repo/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java
Dave Ward b794365eaa Merged V4.1-BUG-FIX to HEAD
42933: ALF-15328 could we add a sample log4j file in 'extension'? 
   42935: ALF-16455: Merged V3.4-BUG-FIX (3.4.12) to V4.1-BUG-FIX (4.1.2)
      42934: ALF-16454 TransformerDebug id values have gaps 
   42955: ALF-15328 could we add a sample log4j file in 'extension'?
      - missing j in file name
   42982: Merged DEV to V4.1-BUG-FIX
      42873: ALF-16194: Checkout/Checkin leaves Lockable aspect on which disables autoVersionOnUpdateProps
             DoNothingCopyBehaviourCallback policy has been set for lockable aspect (implemented in LockServiceImpl);
             Unit test has been added. 
   43000: Merged BRANCHES/DEV/BELARUS/V4.1-BUG-FIX-2012_10_19 to BRANCHES/DEV/V4.1-BUG-FIX:
      42936: ALF-11573: It's impossible to Modify settings for document versions
   43010: ALF-16006 MT: Document Library is absent after upgrade from 3.4.x to 4.1.x (eg. 3.4.10 -> 4.1.1)
      - applied patch suggested in JIRA
   43017: ALF-16457: "CmisObjectNotFoundException: No corresponding type found! Not a CMIS object?" thrown by AlfrescoCmisServiceImpl.getChildren
        - Check CMISNodeInfo for invalid type before processing.
   43019: ALF-14353: Upgrade Activiti dependencies in Maven build
   43022: ALF-14353: Upgrade Activiti dependencies in Maven build
   43027: Merged DEV to V4.1-BUG-FIX
       42426: ALF-15577: " does not support the method HEAD " when opening a MS Access file with "View In Browser"
   43029: Merged DEV to 4.1-BUG-FIX (4.1.2)
      42988: ALF-15791: Custom Types,Aspects defined with prefix using underscore cannot be loaded by API calls like api/classes/<type or aspect>
         Identical logic for old and new ClassDef API was moved to abstract super classes
      42924: ALF-15791: Custom Types,Aspects defined with prefix using underscore cannot be loaded by API calls like api/classes/<type or aspect>
         New set of URL templates for class defenitions were provided to support requests with separated namespace prefixes and names
   43031: ALF-16489: Typo in column-name of newly created index
   43041: Merged DEV to 4.1-BUG-FIX (4.1.2)
      43040: ALF-16425: API call to return all classes, returns wrong properties in classes
         Propertydefs and assocdefs are reordered to corelate with classdefs. Unit test was added for issue.
   43052: ALF-16194: Checkout/Checkin leaves Lockable aspect on which disables autoVersionOnUpdateProps
      - Fix failing test
   43055: Probable fix for ALF-15813. Replaced the 'skipCount' with the one in the query request, rather than query result.
   The Lucene query result does not support the reporting of the skipCount.
   43065: Merged V3.4-BUG-FIX to V4.1-BUG-FIX
      42958: ALF-14421: Inconsistencies when applying Versionable Aspect 
      - We think the most preferable fix that will result in the best consistency between Share and old Explorer behaviour is to make the adding of the versionable aspect always result in an initial MAJOR version, if a version type has not been specified. Major/minor versions can still be controlled explicitly by checkout/checkin the versionable aspect properties and the version service API.
      42998: ALF-14421: Fixed version label unit test fallout - back to what it used to be.
      42999: Fix for ALF-16261 - IE script error occurs when email space users providing there are no users in this space
      43006: Removed dependency on Apache Commons StringUtils. See ALF-12541, ALF-14254, AMZNSSS-17
      43028: ALF-14722: Merged V4.1-BUG-FIX to V3.4-BUG-FIX
         42902: Merged DEV to V4.1-BUG-FIX
            42519: ALF-13588: Google Doc failed to authenticate after incorrect password being entered for google account 
               Add ability to unregister class behaviours.
               Unregister googledocs behaviours when subsystem stops.
   43066: ALF-16502: Merged PATCHES/V4.0.2 to V4.1-BUG-FIX
      42969: Merged DEV to PATCHES/V4.0.2
         42967: MNT-158: SharePoint Protocol Opening Documents in Read-Only for Site Consumer with Collaborator Privileges
            Remove manual throwing of AccessDeniedException is user has "consumer" or "contributor" role.
            Create "links" container from system user.
   43067: Merged PATCHES/V4.1.1 to V4.1-BUG-FIX
      42470: ALF-16503 / ALF-16256: Upgrade 3.4.6 HF to 4.1.1 - DuplicateChildNodeNameException: Duplicate child name not allowed: surf-config 
      42591: ALF-16504 / ALF-16332: Fixed synchronization around org.alfresco.repo.dictionary.NamespaceDAOImpl.namespaceRegistryCache
      - There was no 'double checking' after releasing the write lock, meaning that under high concurrency lots of threads would queue up to continuously re-initialize the registry. 
      42705: ALF-16504/ ALF-16332, ALF-16377: Revisited synchronization and initialization of mutually-dependent DictionaryDAO and NamespaceDAO to prevent deadlock and simultaneous re-initialization in more than one thread
   43068: Merged DEV to V4.1-BUG-FIX
      - TODO: Update DB2 DDL in activiti
      42388: ALF-15828: DB2: unexpected index found in database.
      Modify activity create script for db2 to create normal name for ACT_HI_PROCINST.PROC_INST_ID_ index.
      Introduce patch that will rename autogenerated name to normal name for ACT_HI_PROCINST.PROC_INST_ID_ index.
      Update schema reference file for DB2.
      42429: ALF-15828: DB2: unexpected index found in database.
      Fix scripts from ALF-14983 and ALF-16038 to drop/recreate tables in DB2.
   43069: ALF-11214: IMAP subsystem is not successfully restarted after incorrect modification of IMAP properties via Admin Console
      Stopped ChildApplicationContextFactory from caching a stale application context that didn't successfully refresh.
   43071: ALF-13660: When using kerberos SSO, non domain explorer users requesting a download URL get a login page but after login do not get the requested document
   - Now the Web Client authentication filters use the same mechanism for preserving the request URL through a redirect to the login page
   43076: ALF-15828: Fixed merge issue
   43079: ALF-13602: Incorrect number of documents displayed in Share DocLib when a file is checked out
   - added ability for FileFolderService.list (-> FileFolder GetChildren CQ) to filter by one or more aspects, eg. cm:checkedOut
   43080: ALF-14421: More version label unit test fallout
   43092: Fix ALF-16460: Users may receive activity feed entries (from people they follow) for moderated sites to which they do not belong
   - also add unit test
   43093: Fix for ALF-16091 - Unable to inline edit javascript file.
   43096: Fix for ALF-16283 - When document is checked out, 'Edit Online' and 'Upload New Version' options should not be visible on the original document.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@43103 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2012-10-25 16:38:22 +00:00

856 lines
32 KiB
Java

/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.coci;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.BeforeCancelCheckOut;
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.BeforeCheckIn;
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.BeforeCheckOut;
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.OnCancelCheckOut;
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.OnCheckIn;
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.OnCheckOut;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.policy.ClassPolicyDelegate;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.coci.CheckOutCheckInServiceException;
import org.alfresco.service.cmr.lock.LockService;
import org.alfresco.service.cmr.lock.LockStatus;
import org.alfresco.service.cmr.lock.LockType;
import org.alfresco.service.cmr.lock.NodeLockedException;
import org.alfresco.service.cmr.lock.UnableToReleaseLockException;
import org.alfresco.service.cmr.model.FileExistsException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileNotFoundException;
import org.alfresco.service.cmr.repository.AspectMissingException;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.CopyService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.rule.RuleService;
import org.alfresco.service.cmr.rule.RuleType;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.OwnableService;
import org.alfresco.service.cmr.version.VersionService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.I18NUtil;
/**
* Check out check in service implementation
*
* @author Roy Wetherall
*/
public class CheckOutCheckInServiceImpl implements CheckOutCheckInService
{
/**
* I18N labels
*/
private static final String MSG_ERR_BAD_COPY = "coci_service.err_bad_copy";
private static final String MSG_WORKING_COPY_LABEL = "coci_service.working_copy_label";
private static final String MSG_ERR_NOT_OWNER = "coci_service.err_not_owner";
private static final String MSG_ERR_ALREADY_WORKING_COPY = "coci_service.err_workingcopy_checkout";
private static final String MSG_ERR_NOT_AUTHENTICATED = "coci_service.err_not_authenticated";
private static final String MSG_ERR_WORKINGCOPY_HAS_NO_MIMETYPE = "coci_service.err_workingcopy_has_no_mimetype";
private static final String MSG_ALREADY_CHECKEDOUT = "coci_service.err_already_checkedout";
private static final String MSG_ERR_CANNOT_RENAME = "coci_service.err_cannot_rename";
/** Class policy delegate's */
private ClassPolicyDelegate<BeforeCheckOut> beforeCheckOut;
private ClassPolicyDelegate<OnCheckOut> onCheckOut;
private ClassPolicyDelegate<BeforeCheckIn> beforeCheckIn;
private ClassPolicyDelegate<OnCheckIn> onCheckIn;
private ClassPolicyDelegate<BeforeCancelCheckOut> beforeCancelCheckOut;
private ClassPolicyDelegate<OnCancelCheckOut> onCancelCheckOut;
/**
* Extension character, used to recalculate the working copy names
*/
private static final String EXTENSION_CHARACTER = ".";
private static Log logger = LogFactory.getLog(CheckOutCheckInServiceImpl.class);
private NodeService nodeService;
private VersionService versionService;
private LockService lockService;
private CopyService copyService;
private FileFolderService fileFolderService;
private OwnableService ownableService;
private PolicyComponent policyComponent;
private AuthenticationService authenticationService;
private RuleService ruleService;
/** Component to determine which behaviours are active and which not */
private BehaviourFilter behaviourFilter;
/**
* @param behaviourFilter the behaviourFilter to set
*/
public void setBehaviourFilter(BehaviourFilter behaviourFilter)
{
this.behaviourFilter = behaviourFilter;
}
/**
* Set the node service
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* Set the version service
*/
public void setVersionService(VersionService versionService)
{
this.versionService = versionService;
}
/**
* Set the ownable service
*/
public void setOwnableService(OwnableService ownableService)
{
this.ownableService = ownableService;
}
/**
* Sets the lock service
*/
public void setLockService(LockService lockService)
{
this.lockService = lockService;
}
/**
* Sets the copy service
*/
public void setCopyService(CopyService copyService)
{
this.copyService = copyService;
}
/**
* Sets the authentication service
*/
public void setAuthenticationService(AuthenticationService authenticationService)
{
this.authenticationService = authenticationService;
}
/**
* Set the file folder service
*/
public void setFileFolderService(FileFolderService fileFolderService)
{
this.fileFolderService = fileFolderService;
}
/**
* @param policyComponent policy component
*/
public void setPolicyComponent(PolicyComponent policyComponent)
{
this.policyComponent = policyComponent;
}
/**
* @param ruleService rule service
*/
public void setRuleService(RuleService ruleService)
{
this.ruleService = ruleService;
}
/**
* Initialise method
*/
public void init()
{
// Register the policies
beforeCheckOut = policyComponent.registerClassPolicy(CheckOutCheckInServicePolicies.BeforeCheckOut.class);
onCheckOut = policyComponent.registerClassPolicy(CheckOutCheckInServicePolicies.OnCheckOut.class);
beforeCheckIn = policyComponent.registerClassPolicy(CheckOutCheckInServicePolicies.BeforeCheckIn.class);
onCheckIn = policyComponent.registerClassPolicy(CheckOutCheckInServicePolicies.OnCheckIn.class);
beforeCancelCheckOut = policyComponent.registerClassPolicy(CheckOutCheckInServicePolicies.BeforeCancelCheckOut.class);
onCancelCheckOut = policyComponent.registerClassPolicy(CheckOutCheckInServicePolicies.OnCancelCheckOut.class);
}
/**
* Returns all the classes of a node, including its type and aspects.
*
* @param nodeRef node reference
* @return List<QName> list of classes
*/
private List<QName> getInvokeClasses(NodeRef nodeRef)
{
List<QName> result = new ArrayList<QName>(10);
result.add(nodeService.getType(nodeRef));
Set<QName> aspects = nodeService.getAspects(nodeRef);
for (QName aspect : aspects)
{
result.add(aspect);
}
return result;
}
/**
* Invoke the before check out policy
*
* @param nodeRef the node to be checked out
* @param destinationParentNodeRef the parent of the working copy
* @param destinationAssocTypeQName the working copy's primary association type
* @param destinationAssocQName the working copy's primary association name
*/
private void invokeBeforeCheckOut(
NodeRef nodeRef,
NodeRef destinationParentNodeRef,
QName destinationAssocTypeQName,
QName destinationAssocQName)
{
List<QName> classes = getInvokeClasses(nodeRef);
for (QName invokeClass : classes)
{
Collection<BeforeCheckOut> policies = beforeCheckOut.getList(invokeClass);
for (BeforeCheckOut policy : policies)
{
policy.beforeCheckOut(nodeRef, destinationParentNodeRef, destinationAssocTypeQName, destinationAssocQName);
}
}
}
/**
* Invoke on the on check out policy
*
* @param workingCopy the new working copy
*/
private void invokeOnCheckOut(NodeRef workingCopy)
{
List<QName> classes = getInvokeClasses(workingCopy);
for (QName invokeClass : classes)
{
Collection<OnCheckOut> policies = onCheckOut.getList(invokeClass);
for (OnCheckOut policy : policies)
{
policy.onCheckOut(workingCopy);
}
}
}
/**
* Invoke before check in policy
*
* @param workingCopyNodeRef the current working copy to check in
* @param versionProperties
* @param contentUrl
* @param keepCheckedOut
*/
private void invokeBeforeCheckIn(
NodeRef workingCopyNodeRef,
Map<String,Serializable> versionProperties,
String contentUrl,
boolean keepCheckedOut)
{
List<QName> classes = getInvokeClasses(workingCopyNodeRef);
for (QName invokeClass : classes)
{
Collection<BeforeCheckIn> policies = beforeCheckIn.getList(invokeClass);
for (BeforeCheckIn policy : policies)
{
policy.beforeCheckIn(workingCopyNodeRef, versionProperties, contentUrl, keepCheckedOut);
}
}
}
/**
* Invoke on check in policy
*
* @param nodeRef the node being checked in
*/
private void invokeOnCheckIn(NodeRef nodeRef)
{
List<QName> classes = getInvokeClasses(nodeRef);
for (QName invokeClass : classes)
{
Collection<OnCheckIn> policies = onCheckIn.getList(invokeClass);
for (OnCheckIn policy : policies)
{
policy.onCheckIn(nodeRef);
}
}
}
/**
* Invoke before cancel check out
*
* @param workingCopy the working copy that will be destroyed
*/
private void invokeBeforeCancelCheckOut(NodeRef workingCopy)
{
List<QName> classes = getInvokeClasses(workingCopy);
for (QName invokeClass : classes)
{
Collection<BeforeCancelCheckOut> policies = beforeCancelCheckOut.getList(invokeClass);
for (BeforeCancelCheckOut policy : policies)
{
policy.beforeCancelCheckOut(workingCopy);
}
}
}
/**
* Invoke on cancel check out
*
* @param nodeRef the working copy that will be destroyed
*/
private void invokeOnCancelCheckOut(NodeRef nodeRef)
{
List<QName> classes = getInvokeClasses(nodeRef);
for (QName invokeClass : classes)
{
Collection<OnCancelCheckOut> policies = onCancelCheckOut.getList(invokeClass);
for (OnCancelCheckOut policy : policies)
{
policy.onCancelCheckOut(nodeRef);
}
}
}
@Override
public NodeRef checkout(NodeRef nodeRef)
{
// Find the primary parent in order to determine where to put the copy
ChildAssociationRef childAssocRef = nodeService.getPrimaryParent(nodeRef);
// Checkout the working copy to the same destination
return checkout(nodeRef, childAssocRef.getParentRef(), childAssocRef.getTypeQName(), childAssocRef.getQName());
}
@Override
public NodeRef checkout(
final NodeRef nodeRef,
final NodeRef destinationParentNodeRef,
final QName destinationAssocTypeQName,
QName destinationAssocQName)
{
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CHECKED_OUT))
{
throw new CheckOutCheckInServiceException(MSG_ALREADY_CHECKEDOUT);
}
// Make sure we are not checking out a working copy node
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY))
{
throw new CheckOutCheckInServiceException(MSG_ERR_ALREADY_WORKING_COPY);
}
// It is not enough to check LockUtils.isLockedOrReadOnly in case when the same user does offline and online edit (for instance in two open browsers). In this case we get
// set ContentModel.ASPECT_LOCKABLE and LockType.WRITE_LOCK. So, here we have to check following
LockStatus lockStatus = lockService.getLockStatus(nodeRef);
if (lockStatus != LockStatus.NO_LOCK && lockStatus != LockStatus.LOCK_EXPIRED)
{
throw new NodeLockedException(nodeRef);
}
behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
behaviourFilter.disableBehaviour(destinationParentNodeRef, ContentModel.ASPECT_AUDITABLE);
try
{
return doCheckout(nodeRef, destinationParentNodeRef, destinationAssocTypeQName, destinationAssocQName);
}
finally
{
behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
behaviourFilter.enableBehaviour(destinationParentNodeRef, ContentModel.ASPECT_AUDITABLE);
}
}
private NodeRef doCheckout(
final NodeRef nodeRef,
final NodeRef destinationParentNodeRef,
final QName destinationAssocTypeQName,
QName destinationAssocQName)
{
// Apply the lock aspect if required
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE) == false)
{
nodeService.addAspect(nodeRef, ContentModel.ASPECT_LOCKABLE, null);
}
// Invoke before check out policy
invokeBeforeCheckOut(nodeRef, destinationParentNodeRef, destinationAssocTypeQName, destinationAssocQName);
// Get the user
final String userName = getUserName();
NodeRef workingCopy = null;
ruleService.disableRuleType(RuleType.UPDATE);
try
{
// Rename the working copy
String copyName = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
String workingCopyLabel = getWorkingCopyLabel();
copyName = createWorkingCopyName(copyName, workingCopyLabel);
// Make the working copy
final QName copyQName = QName.createQName(destinationAssocQName.getNamespaceURI(), QName.createValidLocalName(copyName));
// Find the primary parent
ChildAssociationRef childAssocRef = nodeService.getPrimaryParent(nodeRef);
// If destination parent for working copy is the same as the parent of the source node
// then working copy should be created even if the user has no permissions to create children in
// the parent of the source node
if (destinationParentNodeRef.equals(childAssocRef.getParentRef()))
{
workingCopy = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<NodeRef>()
{
public NodeRef doWork() throws Exception
{
NodeRef copy = copyService.copy(
nodeRef,
destinationParentNodeRef,
destinationAssocTypeQName,
copyQName);
// Set the owner of the working copy to be the current user
ownableService.setOwner(copy, userName);
return copy;
}
}, AuthenticationUtil.getSystemUserName());
}
else
{
workingCopy = copyService.copy(
nodeRef,
destinationParentNodeRef,
destinationAssocTypeQName,
copyQName);
}
// Update the working copy name
nodeService.setProperty(workingCopy, ContentModel.PROP_NAME, copyName);
// Apply the working copy aspect to the working copy
Map<QName, Serializable> workingCopyProperties = new HashMap<QName, Serializable>(1);
workingCopyProperties.put(ContentModel.PROP_WORKING_COPY_OWNER, userName);
workingCopyProperties.put(ContentModel.PROP_WORKING_COPY_LABEL, workingCopyLabel);
nodeService.addAspect(workingCopy, ContentModel.ASPECT_WORKING_COPY, workingCopyProperties);
nodeService.addAspect(workingCopy, ContentModel.ASPECT_LOCKABLE, null);
nodeService.addAspect(nodeRef, ContentModel.ASPECT_CHECKED_OUT, null);
nodeService.createAssociation(nodeRef, workingCopy, ContentModel.ASSOC_WORKING_COPY_LINK);
}
finally
{
ruleService.enableRuleType(RuleType.UPDATE);
}
// Lock the original node
lockService.lock(nodeRef, LockType.READ_ONLY_LOCK);
// Invoke on check out policy
invokeOnCheckOut(workingCopy);
return workingCopy;
}
/**
* Gets the authenticated users node reference
*
* @return the users node reference
*/
private String getUserName()
{
String un = this.authenticationService.getCurrentUserName();
if (un != null)
{
return un;
}
else
{
throw new CheckOutCheckInServiceException(MSG_ERR_NOT_AUTHENTICATED);
}
}
@Override
public NodeRef checkin(
NodeRef workingCopyNodeRef,
Map<String, Serializable> versionProperties)
{
return checkin(workingCopyNodeRef, versionProperties, null, false);
}
@Override
public NodeRef checkin(
NodeRef workingCopyNodeRef,
Map<String, Serializable> versionProperties,
String contentUrl)
{
return checkin(workingCopyNodeRef, versionProperties, contentUrl, false);
}
@Override
public NodeRef checkin(
NodeRef workingCopyNodeRef,
Map<String,Serializable> versionProperties,
String contentUrl,
boolean keepCheckedOut)
{
// Check that we have been handed a working copy
if (!nodeService.hasAspect(workingCopyNodeRef, ContentModel.ASPECT_WORKING_COPY))
{
// Error since we have not been passed a working copy
throw new AspectMissingException(ContentModel.ASPECT_WORKING_COPY, workingCopyNodeRef);
}
// Get the checked out node
NodeRef nodeRef = getCheckedOut(workingCopyNodeRef);
if (nodeRef == null)
{
// Error since the original node can not be found
throw new CheckOutCheckInServiceException(MSG_ERR_BAD_COPY);
}
// Invoke policy
invokeBeforeCheckIn(workingCopyNodeRef, versionProperties, contentUrl, keepCheckedOut);
try
{
// Release the lock
lockService.unlock(nodeRef);
}
catch (UnableToReleaseLockException exception)
{
throw new CheckOutCheckInServiceException(MSG_ERR_NOT_OWNER, exception);
}
if (contentUrl != null)
{
ContentData contentData = (ContentData) nodeService.getProperty(workingCopyNodeRef, ContentModel.PROP_CONTENT);
if (contentData == null)
{
throw new AlfrescoRuntimeException(MSG_ERR_WORKINGCOPY_HAS_NO_MIMETYPE, new Object[]{workingCopyNodeRef});
}
else
{
contentData = new ContentData(
contentUrl,
contentData.getMimetype(),
contentData.getSize(),
contentData.getEncoding());
}
// Set the content url value onto the working copy
nodeService.setProperty(
workingCopyNodeRef,
ContentModel.PROP_CONTENT,
contentData);
}
// Copy the contents of the working copy onto the original
this.copyService.copy(workingCopyNodeRef, nodeRef);
// Handle name change on working copy (only for folders/files)
if (fileFolderService.getFileInfo(workingCopyNodeRef) != null)
{
String origName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
String name = (String)nodeService.getProperty(workingCopyNodeRef, ContentModel.PROP_NAME);
String wcLabel = (String)this.nodeService.getProperty(workingCopyNodeRef, ContentModel.PROP_WORKING_COPY_LABEL);
if (wcLabel == null)
{
wcLabel = getWorkingCopyLabel();
}
if (hasWorkingCopyNameChanged(name, origName, wcLabel))
{
// ensure working copy has working copy label in its name to avoid name clash
if (!name.contains(" " + wcLabel))
{
try
{
fileFolderService.rename(workingCopyNodeRef, createWorkingCopyName(name, wcLabel));
}
catch (FileExistsException e)
{
throw new CheckOutCheckInServiceException(e, MSG_ERR_CANNOT_RENAME, name, wcLabel);
}
catch (FileNotFoundException e)
{
throw new CheckOutCheckInServiceException(e, MSG_ERR_CANNOT_RENAME, name, wcLabel);
}
}
String newName = getNameFromWorkingCopyName(name, wcLabel);
try
{
// rename original to changed working name
fileFolderService.rename(nodeRef, newName);
}
catch (FileExistsException e)
{
throw new CheckOutCheckInServiceException(e, MSG_ERR_CANNOT_RENAME, origName, newName);
}
catch (FileNotFoundException e)
{
throw new CheckOutCheckInServiceException(e, MSG_ERR_CANNOT_RENAME, name, newName);
}
}
}
if (versionProperties != null && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE))
{
// Create the new version
this.versionService.createVersion(nodeRef, versionProperties);
}
if (keepCheckedOut == false)
{
// Delete the working copy
behaviourFilter.disableBehaviour(workingCopyNodeRef, ContentModel.ASPECT_WORKING_COPY);
try
{
// Clean up original node
// Note: Lock has already been removed. So no lockService.unlock(nodeRef);
nodeService.removeAspect(nodeRef, ContentModel.ASPECT_CHECKED_OUT);
// Delete the working copy
nodeService.deleteNode(workingCopyNodeRef);
}
finally
{
// Just for symmetry; the node is gone
behaviourFilter.enableBehaviour(workingCopyNodeRef, ContentModel.ASPECT_WORKING_COPY);
}
}
else
{
// Re-lock the original node
lockService.lock(nodeRef, LockType.READ_ONLY_LOCK);
}
// Invoke policy
invokeOnCheckIn(nodeRef);
return nodeRef;
}
@Override
public NodeRef cancelCheckout(NodeRef workingCopyNodeRef)
{
// Check that we have been handed a working copy
if (!nodeService.hasAspect(workingCopyNodeRef, ContentModel.ASPECT_WORKING_COPY))
{
// Error since we have not been passed a working copy
throw new AspectMissingException(ContentModel.ASPECT_WORKING_COPY, workingCopyNodeRef);
}
// Get the checked out node
NodeRef nodeRef = getCheckedOut(workingCopyNodeRef);
if (nodeRef == null)
{
// Error since the original node can not be found
throw new CheckOutCheckInServiceException(MSG_ERR_BAD_COPY);
}
// Invoke policy
invokeBeforeCancelCheckOut(workingCopyNodeRef);
behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
behaviourFilter.disableBehaviour(workingCopyNodeRef, ContentModel.ASPECT_WORKING_COPY);
try
{
// Release the lock on the original node
lockService.unlock(nodeRef);
nodeService.removeAspect(nodeRef, ContentModel.ASPECT_CHECKED_OUT);
// Delete the working copy
nodeService.deleteNode(workingCopyNodeRef);
// Invoke policy
invokeOnCancelCheckOut(nodeRef);
}
catch (UnableToReleaseLockException exception)
{
throw new CheckOutCheckInServiceException(MSG_ERR_NOT_OWNER, exception);
}
finally
{
behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
}
return nodeRef;
}
@Override
public NodeRef getWorkingCopy(NodeRef nodeRef)
{
NodeRef workingCopy = null;
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CHECKED_OUT))
{
List<AssociationRef> assocs = nodeService.getTargetAssocs(nodeRef, ContentModel.ASSOC_WORKING_COPY_LINK);
// It is a 1:1 relationship
if (assocs.size() > 0)
{
if (assocs.size() > 1)
{
logger.warn("Found multiple " + ContentModel.ASSOC_WORKING_COPY_LINK + " association from node: " + nodeRef);
}
workingCopy = assocs.get(0).getTargetRef();
}
}
return workingCopy;
}
@Override
public NodeRef getCheckedOut(NodeRef nodeRef)
{
NodeRef original = null;
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY))
{
List<AssociationRef> assocs = nodeService.getSourceAssocs(nodeRef, ContentModel.ASSOC_WORKING_COPY_LINK);
// It is a 1:1 relationship
if (assocs.size() > 0)
{
if (assocs.size() > 1)
{
logger.warn("Found multiple " + ContentModel.ASSOC_WORKING_COPY_LINK + " associations to node: " + nodeRef);
}
original = assocs.get(0).getSourceRef();
}
}
return original;
}
@Override
public boolean isWorkingCopy(NodeRef nodeRef)
{
return nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY);
}
@Override
public boolean isCheckedOut(NodeRef nodeRef)
{
return nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CHECKED_OUT);
}
/**
* Create a working copy name using the given fileName and workingCopyLabel. The label will be inserted before
* the file extension (if present), or else appended to the name (in either case a space is prepended to the
* workingCopyLabel).
* <p>
* Examples, where workingCopyLabel is "wc":
* <p>
* "Myfile.txt" becomes "Myfile wc.txt", "Myfile" becomes "Myfile wc".
* <p>
* In the event that fileName is empty or null, the workingCopyLabel is used for the new working copy name
* <p>
* Example: "" becomes "wc".
*
* @param name
* @param workingCopyLabel
* @return
*/
public static String createWorkingCopyName(String name, final String workingCopyLabel)
{
if (workingCopyLabel != null && workingCopyLabel.length() != 0)
{
if (name != null && name.length() != 0)
{
int index = name.lastIndexOf(EXTENSION_CHARACTER);
if (index > 0)
{
// Insert the working copy label before the file extension
name = name.substring(0, index) + " " + workingCopyLabel + name.substring(index);
}
else
{
// Simply append the working copy label onto the end of the existing name
name = name + " " + workingCopyLabel;
}
}
else
{
name = workingCopyLabel;
}
}
return name;
}
/**
* Get original name from the working copy name and the cm:workingCopyLabel
* that was used to create it.
*
* @param workingCopyLabel
* @return original name
*/
private String getNameFromWorkingCopyName(String workingCopyName, String workingCopyLabel)
{
String workingCopyLabelRegEx = workingCopyLabel.replaceAll("\\(", "\\\\(");
workingCopyLabelRegEx = workingCopyLabelRegEx.replaceAll("\\)", "\\\\)");
if (workingCopyName.contains(" " + workingCopyLabel))
{
workingCopyName = workingCopyName.replaceFirst(" " + workingCopyLabelRegEx, "");
}
else if (workingCopyName.contains(workingCopyLabel))
{
workingCopyName = workingCopyName.replaceFirst(workingCopyLabelRegEx, "");
}
return workingCopyName;
}
/**
* Has the working copy name changed compared to the original name
*
* @param workingCopyName working copy name
* @param origName original name
* @param wcLabel that was inserted into origName to create the original working copy name
* @return true => if changed
*/
private boolean hasWorkingCopyNameChanged(String workingCopyName, String origName, String wcLabel)
{
String origWorkingCopyName = createWorkingCopyName(origName, wcLabel);
return !workingCopyName.equals(origWorkingCopyName);
}
/**
* Get the working copy label.
*
* @return the working copy label
*/
public static String getWorkingCopyLabel()
{
return I18NUtil.getMessage(MSG_WORKING_COPY_LABEL);
}
}