/* * Copyright (C) 2005-2013 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 . */ 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; private ClassPolicyDelegate onCheckOut; private ClassPolicyDelegate beforeCheckIn; private ClassPolicyDelegate onCheckIn; private ClassPolicyDelegate beforeCancelCheckOut; private ClassPolicyDelegate 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 list of classes */ private List getInvokeClasses(NodeRef nodeRef) { List result = new ArrayList(10); result.add(nodeService.getType(nodeRef)); Set 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 classes = getInvokeClasses(nodeRef); for (QName invokeClass : classes) { Collection 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 classes = getInvokeClasses(workingCopy); for (QName invokeClass : classes) { Collection 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 versionProperties, String contentUrl, boolean keepCheckedOut) { List classes = getInvokeClasses(workingCopyNodeRef); for (QName invokeClass : classes) { Collection 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 classes = getInvokeClasses(nodeRef); for (QName invokeClass : classes) { Collection 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 classes = getInvokeClasses(workingCopy); for (QName invokeClass : classes) { Collection 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 classes = getInvokeClasses(nodeRef); for (QName invokeClass : classes) { Collection 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) { // Invoke before check out policy invokeBeforeCheckOut(nodeRef, destinationParentNodeRef, destinationAssocTypeQName, destinationAssocQName); // Apply the lock aspect if required if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE) == false) { nodeService.addAspect(nodeRef, ContentModel.ASPECT_LOCKABLE, null); } // 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() { 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 workingCopyProperties = new HashMap(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 versionProperties) { return checkin(workingCopyNodeRef, versionProperties, null, false); } @Override public NodeRef checkin( NodeRef workingCopyNodeRef, Map versionProperties, String contentUrl) { return checkin(workingCopyNodeRef, versionProperties, contentUrl, false); } @Override public NodeRef checkin( NodeRef workingCopyNodeRef, Map 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 // Disable cm:auditable aspect // See MNT-8789 behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); 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); behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); } } 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 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 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). *

* Examples, where workingCopyLabel is "wc": *

* "Myfile.txt" becomes "Myfile wc.txt", "Myfile" becomes "Myfile wc". *

* In the event that fileName is empty or null, the workingCopyLabel is used for the new working copy name *

* 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); } }