mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
44796: Fix for ALF-16413 - Share asks for Basic-Auth while not needed trying to access RSS feeds (thus breaking SSO). - Share Feed Controller which correctly deals with SSO config for the "alfresco-feed" endpoint Configure the "alfresco-feed" endpoint to use SSO in the same way the "alfresco" endpoint is configured for it. Share will then detect this when serving feeds and ensure the SSO auth is used ahead of Basic HTTP auth. 44820: Merged V3.4-BUG-FIX (3.4.12) to V4.1-BUG-FIX (4.1.3) - A few extra 4.x changes were required 44818: ALF-17256 (3.4.12) Update Copyright notice to 2013 44831: ALF-17224 (All wiki pages are enumerated/built to display a single wiki page) 44841: ALF-17206 CIFS loses metadata when metadata edited from Windows 7 Explorer 44844: Incremented version revision to 4.1.4 44848: Fix for ALF-17178 SolrLuceneAnalyser.findAnalyser generating InavlidQNameExceptions wher they are easily protected. 44849: Fix for ALF-17162 Queries for content properties with a long search string causes huge amount of memory usage 44851: ALF-17224: Improvements for the wiki dashlet 44866: Merged PATCHES/V4.1.1 to V4.1-BUG-FIX 44663: ALF-17281 / MNT-231: Unable to cancel editing on certain docs in 4.1.1.10 - It's now impossible for WebDAV or anything else execpt CheckOutCheckInService to unlock a checked out node - It's also now possible to un check out / check in broken unlocked nodes, such as those on ts.alfresco.com! - Unit tests by Viachaslau Tikhanovich 44664: ALF-17281 / MNT-231: Unable to cancel editing on certain docs in 4.1.1.10 - File missed in previous checkin 44867: ALF-17285: Merged PATCHES/V4.0.1 to V4.1-BUG-FIX 44766: MNT-241: Severe performance issues with WebDAV / filesystem / IMAP rename operations - FileFolderServiceImpl.rename calls moveNode to do its renaming work - Unfortunately AbstractNodeDAOImpl.moveNode() was not optimized for the rename case and attempted to cascade-recompute ACLs on a simple folder rename - On a large hierarchy this could result in hanging transactions and delays of several minutes whilst all the node ACLs were repointed and all the node caches were invalidate 44787: MNT-241: Fixed merge issue. 44823: MNT-241: Severe performance issues with WebDAV / filesystem / IMAP rename operations - The last optimization revealed a caching problem - The childByName cache was retaining stale values because node renaming wasn't incrementing the node version key - We were previously relying on the unnecessary ACL re-evaluation on a move to 'bump' the version key and invalidate the childByName cache as a side effect - Now we explicitly invalidate childByNameCache when necessary and also update parent association rows individually rather than in bulk, hopefully avoiding unnecessary database lock contention 44830: MNT-241: Subtlety: On rename we only update and invalidate those associations for which name uniqueness checking is enforced. Such associations have a positive CRC 44868: Merged PATCHES/V4.1.3 to V4.1-BUG-FIX (RECORD ONLY) 44845: Incremented version revision to 4.1.3 44847: Merged PATCHES/V4.1.1 to PATCHES/V4.1.3 44863: ALF-17285: Merged PATCHES/V4.0.1 to PATCHES/V4.1.3 44864: ALF-15935: Merge V4.1-BUG-FIX to V4.1.3 44029 : MNT-180 - Clone for Hotfix: Word document on Windows via CIFS becomes locked (Read Only) when network drops temporarily 44865: Merged V4.1-BUG-FIX to PATCHES/V4.1.3 44872: Merged PATCHES/V4.1.3 to V4.1-BUG-FIX 44871: Fixed merge issue 44875: Merged V4.1-BUG-FIX (4.1.2) to V3.4-BUG-FIX (3.4.12) RECORD ONLY 44815: Merged V4.1-BUG-FIX to V3.4-BUG-FIX 44776: ALF-17164: Fix failing build in case build is not run in continuous mode - move generation of version.properties out of continuous mode 44874: ALF-17283: Merged V4.1-BUG-FIX (4.1.2) to V3.4-BUG-FIX (3.4.12) 41411: Fix possible FTP data session leak if client mixes PORT and PASV commands. ALF-15126 44876: Merged DEV to V4.1-BUG-FIX 44838: ALF-14468: Unable to authorize to Facebook Add 'www' to 'alfresco.com' urls. 44878: ALF-17208 - category.ftl does not allow to find multiple tags in Share advanced Search 44879: Fix for ALF-17150 - Edit Online action missing in Share for some mime types 44880: Fix to merge fail (rev 44866/44872) 44881: Fix for ALF-17186 - JBOSS specific: Google Docs v2 are not working 44904: Fix build - Merry Christmas! 44906: Merged V3.4-BUG-FIX to V4.1-BUG-FIX 44882: Fix for ALF-13805 - Authenticating Share RSS feed using cookies rather than basic auth 44884: Incremented version revision to 3.4.13 44903: Merged V3.4 to V3.4-BUG-FIX 44885: Fix unit test to cope with ALF-14421 version label behaviour (major unless specified). 44905: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 44883: Merged V3.4-BUG-FIX to V3.4 (3.4.12) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@44910 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
867 lines
33 KiB
Java
867 lines
33 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
|
|
{
|
|
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE))
|
|
{
|
|
// Release the lock on the original node
|
|
lockService.unlock(nodeRef, false, true);
|
|
}
|
|
}
|
|
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
|
|
{
|
|
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE))
|
|
{
|
|
// Release the lock on the original node
|
|
lockService.unlock(nodeRef, false, true);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new IllegalArgumentException("workingCopyLabel is null or empty");
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|