diff --git a/config/alfresco/cmis-ws-context.xml b/config/alfresco/cmis-ws-context.xml index 2c2023417e..340c38b15b 100644 --- a/config/alfresco/cmis-ws-context.xml +++ b/config/alfresco/cmis-ws-context.xml @@ -5,7 +5,7 @@ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"> - + diff --git a/config/alfresco/cxf/cxf.xml b/config/alfresco/cxf/cxf.xml new file mode 100644 index 0000000000..182b0f6d18 --- /dev/null +++ b/config/alfresco/cxf/cxf.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/remote-api-context.xml b/config/alfresco/remote-api-context.xml index 4677a0cd40..c38e96e298 100644 --- a/config/alfresco/remote-api-context.xml +++ b/config/alfresco/remote-api-context.xml @@ -25,10 +25,51 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${system.webdav.renameShufflePattern} + + + ${system.webdav.url.path.prefix} + + \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/comments/comment.lib.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/comments/comment.lib.ftl index 74d3a152ac..4532285dbf 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/comments/comment.lib.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/comments/comment.lib.ftl @@ -43,7 +43,7 @@ "edit": false, "delete": false <#else> - "edit": ${item.node.hasPermission("Write")?string}, + "edit": ${item.canEditComment?string}, "delete": ${item.node.hasPermission("Delete")?string} } diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/comments/comments.lib.js b/config/alfresco/templates/webscripts/org/alfresco/repository/comments/comments.lib.js index b2cd129299..cf54fa8a53 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/comments/comments.lib.js +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/comments/comments.lib.js @@ -63,6 +63,8 @@ function getCommentData(node) data.node = node; data.author = people.getPerson(node.properties["cm:creator"]); data.isUpdated = (node.properties["cm:modified"] - node.properties["cm:created"]) > 5000; + data.canEditComment = (person == data.owner) || (person == data.author) || + node.hasPermission("SiteManager") || node.hasPermission("Coordinator"); return data; } diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index b88af63776..b81f1f34f7 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -1532,6 +1532,7 @@ + transformers = contentService.getActiveTransformers(reader.getMimetype(), sourceSize, MimetypeMap.MIMETYPE_TEXT_PLAIN, options); + transformerDebug.availableTransformers(transformers, sourceSize, "NodeContentGet"); - if(transformException == null) - { - // point the reader to the new-written content - textReader = writer.getReader(); - // Check that the reader is a view onto something concrete - if (textReader == null || !textReader.exists()) + if (transformers.isEmpty()) { - transformException = new ContentIOException( - "The transformation did not write any content, yet: \n" - + " transformer: " + transformer + "\n" + " temp writer: " + writer); + res.setHeader(TRANSFORM_STATUS_HEADER, "noTransform"); + res.setStatus(HttpStatus.SC_NO_CONTENT); + return; + } + ContentTransformer transformer = transformers.get(0); + + // Perform transformation catering for mimetype AND encoding + ContentWriter writer = contentService.getTempWriter(); + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.setEncoding("UTF-8"); // Expect transformers to produce UTF-8 + + try + { + long start = System.currentTimeMillis(); + transformer.transform(reader, writer); + long transformDuration = System.currentTimeMillis() - start; + res.setHeader(TRANSFORM_DURATION_HEADER, String.valueOf(transformDuration)); + } + catch (ContentIOException e) + { + transformException = e; + } + + if(transformException == null) + { + // point the reader to the new-written content + textReader = writer.getReader(); + // Check that the reader is a view onto something concrete + if (textReader == null || !textReader.exists()) + { + transformException = new ContentIOException( + "The transformation did not write any content, yet: \n" + + " transformer: " + transformer + "\n" + " temp writer: " + writer); + } + } + + if(transformException != null) + { + res.setHeader(TRANSFORM_STATUS_HEADER, "transformFailed"); + res.setHeader(TRANSFORM_EXCEPTION_HEADER, transformException.getMessage()); + res.setStatus(HttpStatus.SC_NO_CONTENT); + } + else + { + res.setStatus(HttpStatus.SC_OK); + streamContentImpl(req, res, textReader, false, modified, String.valueOf(modified.getTime()), null); } } - - if(transformException != null) + finally { - res.setHeader(TRANSFORM_STATUS_HEADER, "transformFailed"); - res.setHeader(TRANSFORM_EXCEPTION_HEADER, transformException.getMessage()); - res.setStatus(HttpStatus.SC_NO_CONTENT); - } - else - { - res.setStatus(HttpStatus.SC_OK); - streamContentImpl(req, res, textReader, false, modified, String.valueOf(modified.getTime()), null); + transformerDebug.popAvailable(); } } } diff --git a/source/java/org/alfresco/repo/webdav/AbstractMoveOrCopyMethod.java b/source/java/org/alfresco/repo/webdav/AbstractMoveOrCopyMethod.java deleted file mode 100644 index 4eb22194be..0000000000 --- a/source/java/org/alfresco/repo/webdav/AbstractMoveOrCopyMethod.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * 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 . - */ -package org.alfresco.repo.webdav; - -import javax.servlet.http.HttpServletResponse; - -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; - -/** - * Implements the WebDAV COPY and MOVE methods - * - * @author Derek Hulley - */ -public abstract class AbstractMoveOrCopyMethod extends HierarchicalMethod -{ - /** - * Default constructor - */ - public AbstractMoveOrCopyMethod() - { - } - - /** - * Implement the move or copy, depending on the implementation - * - * @param fileFolderService the service to do the work - * @param sourceNodeRef the node to copy or move - * @param destParentNodeRef the destination parent - * @param name the name of the file or folder - * @throws Exception - */ - protected abstract void moveOrCopy( - FileFolderService fileFolderService, - NodeRef sourceNodeRef, - NodeRef sourceParentNodeRef, - NodeRef destParentNodeRef, - String name) throws Exception; - - /** - * Exceute the request - * - * @exception WebDAVServerException - */ - protected final void executeImpl() throws WebDAVServerException, Exception - { - FileFolderService fileFolderService = getFileFolderService(); - - NodeRef rootNodeRef = getRootNodeRef(); - String servletPath = getServletPath(); - - // Debug - if (logger.isDebugEnabled()) - { - logger.debug("Copy from " + getPath() + " to " + getDestinationPath()); - } - - // the source must exist - String sourcePath = getPath(); - FileInfo sourceInfo = null; - try - { - sourceInfo = getDAVHelper().getNodeForPath(rootNodeRef, sourcePath, servletPath); - } - catch (FileNotFoundException e) - { - throw new WebDAVServerException(HttpServletResponse.SC_NOT_FOUND); - } - - FileInfo sourceParentInfo = getDAVHelper().getParentNodeForPath(rootNodeRef, sourcePath, servletPath); - - // the destination parent must exist - String destPath = getDestinationPath(); - FileInfo destParentInfo = null; - try - { - if (destPath.endsWith(WebDAVHelper.PathSeperator)) - { - destPath = destPath.substring(0, destPath.length() - 1); - } - destParentInfo = getDAVHelper().getParentNodeForPath(rootNodeRef, destPath, servletPath); - } - catch (FileNotFoundException e) - { - if (logger.isDebugEnabled()) - { - logger.debug("Destination parent folder doesn't exist: " + destPath); - } - throw new WebDAVServerException(HttpServletResponse.SC_CONFLICT); - } - - // check for the existence of the destination node - FileInfo destInfo = null; - try - { - destInfo = getDAVHelper().getNodeForPath(rootNodeRef, destPath, servletPath); - if (!destInfo.getNodeRef().equals(sourceInfo.getNodeRef())) - { - // ALF-7079 fix, if destInfo is working copy then content will be updated later - boolean isDestWorkingCopy = getNodeService().hasAspect(destInfo.getNodeRef(), ContentModel.ASPECT_WORKING_COPY); - if (!hasOverWrite() && !isDestWorkingCopy) - { - if (logger.isDebugEnabled()) - { - logger.debug("Destination exists but overwrite is not allowed"); - } - // it exists and we may not overwrite - throw new WebDAVServerException(HttpServletResponse.SC_PRECONDITION_FAILED); - } - // delete the destination node if it is not the same as the source node and not a working copy - if (!isDestWorkingCopy && !isShuffleOperation(sourceInfo) && !isVersioned(destInfo)) - { - checkNode(destInfo); - - // attempting to move or copy onto another node - fileFolderService.delete(destInfo.getNodeRef()); - } - } - } - catch (FileNotFoundException e) - { - // destination doesn't exist - } - - NodeRef sourceNodeRef = sourceInfo.getNodeRef(); - NodeRef sourceParentNodeRef = sourceParentInfo.getNodeRef(); - NodeRef destParentNodeRef = destParentInfo.getNodeRef(); - - String name = getDAVHelper().splitPath(destPath)[1]; - - moveOrCopy(fileFolderService, sourceNodeRef, sourceParentNodeRef, destParentNodeRef, name); - - // Set the response status - if (destInfo == null) - { - m_response.setStatus(HttpServletResponse.SC_CREATED); - } - else - { - m_response.setStatus(HttpServletResponse.SC_NO_CONTENT); - } - } - - - protected boolean isVersioned(FileInfo fileInfo) - { - NodeService nodeService = getNodeService(); - boolean versioned = nodeService.hasAspect(fileInfo.getNodeRef(), ContentModel.ASPECT_VERSIONABLE); - return versioned; - } - - protected boolean isShuffleOperation(FileInfo sourceInfo) - { - NodeService nodeService = getNodeService(); - boolean hidden = nodeService.hasAspect(sourceInfo.getNodeRef(), ContentModel.ASPECT_HIDDEN); - boolean temporary = nodeService.hasAspect(sourceInfo.getNodeRef(), ContentModel.ASPECT_TEMPORARY); - boolean shuffleOperation = hidden && temporary; - return shuffleOperation; - } - - protected void parseRequestHeaders() throws WebDAVServerException - { - super.parseRequestHeaders(); - parseIfHeader(); - } -} diff --git a/source/java/org/alfresco/repo/webdav/CopyMethod.java b/source/java/org/alfresco/repo/webdav/CopyMethod.java index d5c5313e43..4779fc8437 100644 --- a/source/java/org/alfresco/repo/webdav/CopyMethod.java +++ b/source/java/org/alfresco/repo/webdav/CopyMethod.java @@ -26,7 +26,7 @@ import org.alfresco.service.cmr.repository.NodeRef; * * @author Derek Hulley */ -public class CopyMethod extends AbstractMoveOrCopyMethod +public class CopyMethod extends MoveMethod { /** * Default constructor @@ -35,14 +35,13 @@ public class CopyMethod extends AbstractMoveOrCopyMethod { } + /* + * (non-Javadoc) + * @see org.alfresco.repo.webdav.MoveMethod#isMove() + */ @Override - protected void moveOrCopy( - FileFolderService fileFolderService, - NodeRef sourceNodeRef, - NodeRef sourceParentNodeRef, - NodeRef destParentNodeRef, - String name) throws Exception + protected boolean isMove() { - fileFolderService.copy(sourceNodeRef, destParentNodeRef, name); + return false; } } diff --git a/source/java/org/alfresco/repo/webdav/DeleteMethod.java b/source/java/org/alfresco/repo/webdav/DeleteMethod.java index 5ba8e272db..c2dbb3b48f 100644 --- a/source/java/org/alfresco/repo/webdav/DeleteMethod.java +++ b/source/java/org/alfresco/repo/webdav/DeleteMethod.java @@ -29,6 +29,7 @@ import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.cmr.webdav.WebDavService; +import org.alfresco.service.cmr.repository.NodeService; /** * Implements the WebDAV DELETE method @@ -101,18 +102,30 @@ public class DeleteMethod extends WebDAVMethod implements ActivityPostProducer checkNode(fileInfo); - // ALF-7079 fix, working copies are not deleted at all - if (!getNodeService().hasAspect(fileInfo.getNodeRef(), ContentModel.ASPECT_WORKING_COPY)) + NodeService nodeService = getNodeService(); + NodeRef nodeRef = fileInfo.getNodeRef(); + // MNT-181: working copies and versioned nodes are hidden rather than deleted + if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) || nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) + { + setHidden(nodeRef, true); + getDAVLockService().unlock(nodeRef); + } + // We just ensure already-hidden nodes are left unlocked + else if (isHidden(nodeRef)) + { + getDAVLockService().unlock(nodeRef); + } + // A 'real' delete + else { // As this content will be deleted, we need to extract some info before it's no longer available. String siteId = getSiteId(); NodeRef deletedNodeRef = fileInfo.getNodeRef(); - FileInfo parentFile = getDAVHelper().getParentNodeForPath(getRootNodeRef(), getPath(), getServletPath()); - boolean hidden = fileInfo.isHidden(); + FileInfo parentFile = getDAVHelper().getParentNodeForPath(getRootNodeRef(), path, getServletPath()); // Delete it fileFolderService.delete(deletedNodeRef); // Don't post activity data for hidden files, resource forks etc. - if (!hidden) + if (!getDAVHelper().isRenameShuffle(path)) { postActivity(parentFile, fileInfo, siteId); } diff --git a/source/java/org/alfresco/repo/webdav/MoveMethod.java b/source/java/org/alfresco/repo/webdav/MoveMethod.java index e206000319..c15853f2e1 100644 --- a/source/java/org/alfresco/repo/webdav/MoveMethod.java +++ b/source/java/org/alfresco/repo/webdav/MoveMethod.java @@ -31,7 +31,6 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; import org.dom4j.DocumentHelper; import org.dom4j.io.XMLWriter; import org.xml.sax.Attributes; @@ -40,8 +39,9 @@ import org.xml.sax.Attributes; * Implements the WebDAV MOVE method * * @author Derek Hulley + * @author David Ward */ -public class MoveMethod extends AbstractMoveOrCopyMethod +public class MoveMethod extends HierarchicalMethod { /** * Default constructor @@ -49,14 +49,120 @@ public class MoveMethod extends AbstractMoveOrCopyMethod public MoveMethod() { } + + protected boolean isMove() + { + return true; + } + + /** + * Exceute the request + * + * @exception WebDAVServerException + */ + protected final void executeImpl() throws WebDAVServerException, Exception + { + NodeRef rootNodeRef = getRootNodeRef(); + String servletPath = getServletPath(); + // Debug + if (logger.isDebugEnabled()) + { + logger.debug((isMove() ? "Move" : "Copy") + " from " + getPath() + " to " + getDestinationPath()); + } + + // the source must exist + String sourcePath = getPath(); + FileInfo sourceInfo = null; + try + { + sourceInfo = getDAVHelper().getNodeForPath(rootNodeRef, sourcePath, servletPath); + } + catch (FileNotFoundException e) + { + throw new WebDAVServerException(HttpServletResponse.SC_NOT_FOUND); + } + + FileInfo sourceParentInfo = getDAVHelper().getParentNodeForPath(rootNodeRef, sourcePath, servletPath); + + // the destination parent must exist + String destPath = getDestinationPath(); + FileInfo destParentInfo = null; + try + { + if (destPath.endsWith(WebDAVHelper.PathSeperator)) + { + destPath = destPath.substring(0, destPath.length() - 1); + } + destParentInfo = getDAVHelper().getParentNodeForPath(rootNodeRef, destPath, servletPath); + } + catch (FileNotFoundException e) + { + if (logger.isDebugEnabled()) + { + logger.debug("Destination parent folder doesn't exist: " + destPath); + } + throw new WebDAVServerException(HttpServletResponse.SC_CONFLICT); + } + + // check for the existence of the destination node + FileInfo destInfo = null; + boolean destExists = false; + try + { + destInfo = getDAVHelper().getNodeForPath(rootNodeRef, destPath, servletPath); + if (!destInfo.getNodeRef().equals(sourceInfo.getNodeRef())) + { + // ALF-7079 fix, if destInfo is a hidden shuffle target then pretend it's not there + destExists = !isHidden(destInfo.getNodeRef()); + if (!hasOverWrite() && destExists) + { + if (logger.isDebugEnabled()) + { + logger.debug("Destination exists but overwrite is not allowed"); + } + // it exists and we may not overwrite + throw new WebDAVServerException(HttpServletResponse.SC_PRECONDITION_FAILED); + } + checkNode(destInfo); + } + } + catch (FileNotFoundException e) + { + // destination doesn't exist + } + + NodeRef sourceNodeRef = sourceInfo.getNodeRef(); + NodeRef sourceParentNodeRef = sourceParentInfo.getNodeRef(); + NodeRef destParentNodeRef = destParentInfo.getNodeRef(); + + String name = getDAVHelper().splitPath(destPath)[1]; + + moveOrCopy(sourceNodeRef, sourceParentNodeRef, destParentNodeRef, name); + + // Set the response status + if (!destExists) + { + m_response.setStatus(HttpServletResponse.SC_CREATED); + } + else + { + m_response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + } + + protected void parseRequestHeaders() throws WebDAVServerException + { + super.parseRequestHeaders(); + parseIfHeader(); + } protected void moveOrCopy( - FileFolderService fileFolderService, NodeRef sourceNodeRef, NodeRef sourceParentNodeRef, NodeRef destParentNodeRef, String name) throws Exception { + FileFolderService fileFolderService = getFileFolderService(); NodeRef rootNodeRef = getRootNodeRef(); String sourcePath = getPath(); @@ -67,8 +173,8 @@ public class MoveMethod extends AbstractMoveOrCopyMethod List destPathElements = getDAVHelper().splitAllPaths(destPath); FileInfo destFileInfo = null; - NodeService nodeService = getNodeService(); - + boolean isMove = isMove(); + try { // get the node to move @@ -87,12 +193,40 @@ public class MoveMethod extends AbstractMoveOrCopyMethod throw new WebDAVServerException(HttpServletResponse.SC_NOT_FOUND); } } + if (isMove) + { + checkNode(sourceFileInfo); + } + // ALF-7079 fix, if destination exists then its content is updated with source content and source is deleted if + // this is a move + if (destFileInfo != null) + { + copyContentOnly(sourceNodeRef, destFileInfo, fileFolderService); + setHidden(destFileInfo.getNodeRef(), false); + if (isMove) + { + fileFolderService.delete(sourceNodeRef); + } + } + // If this is a copy then the source is just copied to destination. + else if (!isMove) + { + fileFolderService.copy(sourceNodeRef, destParentNodeRef, name); + } + // If this is a move and the destination looks like the start of a shuffle operation, then the source is just + // copied to destination and the source is hidden. + else if (getDAVHelper().isRenameShuffle(destPath) && !getDAVHelper().isRenameShuffle(sourcePath)) + { + destFileInfo = fileFolderService.create(destParentNodeRef, name, ContentModel.TYPE_CONTENT); + copyContentOnly(sourceNodeRef, destFileInfo, fileFolderService); + setHidden(sourceNodeRef, true); - checkNode(sourceFileInfo); - - if (sourceParentNodeRef.equals(destParentNodeRef)) + // As per the WebDAV spec, we make sure the node is unlocked once moved + getDAVHelper().getLockService().unlock(sourceNodeRef); + } + else if (sourceParentNodeRef.equals(destParentNodeRef)) { - // It is rename method + // It is a simple rename operation try { fileFolderService.rename(sourceNodeRef, name); @@ -111,43 +245,23 @@ public class MoveMethod extends AbstractMoveOrCopyMethod xml.endElement(WebDAV.DAV_NS, WebDAV.XML_ERROR, WebDAV.XML_NS_ERROR); m_response.setStatus(HttpServletResponse.SC_CONFLICT); - return; } } - else if (destFileInfo != null && (isShuffleOperation(sourceFileInfo) || isVersioned(destFileInfo))) - { - copyOnlyContent(sourceNodeRef, destFileInfo, fileFolderService); - } - else - // ALF-7079 fix, if source is working copy then it is just copied to destination - if (nodeService.hasAspect(sourceNodeRef, ContentModel.ASPECT_WORKING_COPY)) - { - // replace move with copy action for working copies - fileFolderService.copy(sourceNodeRef, destParentNodeRef, name); - } - // ALF-7079 fix, if destination exists and is working copy then its content is updated with - // source content and source is deleted - else if (destFileInfo != null && nodeService.hasAspect(destFileInfo.getNodeRef(), ContentModel.ASPECT_WORKING_COPY)) - { - // copy only content for working copy destination - copyOnlyContent(sourceNodeRef, destFileInfo, fileFolderService); - } else { // It is a simple move operation fileFolderService.moveFrom(sourceNodeRef, sourceParentNodeRef, destParentNodeRef, name); + + // As per the WebDAV spec, we make sure the node is unlocked once moved + getDAVHelper().getLockService().unlock(sourceNodeRef); } - // As per the WebDAV spec, we make sure the node is unlocked once moved - getDAVHelper().getLockService().unlock(sourceNodeRef); } - private void copyOnlyContent(NodeRef sourceNodeRef, FileInfo destFileInfo, FileFolderService fileFolderService) + private void copyContentOnly(NodeRef sourceNodeRef, FileInfo destFileInfo, FileFolderService fileFolderService) { ContentService contentService = getContentService(); ContentReader reader = contentService.getReader(sourceNodeRef, ContentModel.PROP_CONTENT); ContentWriter contentWriter = contentService.getWriter(destFileInfo.getNodeRef(), ContentModel.PROP_CONTENT, true); contentWriter.putContent(reader); - - fileFolderService.delete(sourceNodeRef); } } diff --git a/source/java/org/alfresco/repo/webdav/PropFindMethod.java b/source/java/org/alfresco/repo/webdav/PropFindMethod.java index 921c987e76..3cebb73a24 100644 --- a/source/java/org/alfresco/repo/webdav/PropFindMethod.java +++ b/source/java/org/alfresco/repo/webdav/PropFindMethod.java @@ -36,7 +36,6 @@ import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConverter; import org.alfresco.service.namespace.QName; @@ -195,6 +194,13 @@ public class PropFindMethod extends WebDAVMethod // The path is not valid - send a 404 error back to the client throw new WebDAVServerException(HttpServletResponse.SC_NOT_FOUND); } + + // A node hidden during a 'shuffle' operation - send a 404 error back to the client, as some Mac clients need + // this + if (isHidden(pathNodeInfo.getNodeRef())) + { + throw new WebDAVServerException(HttpServletResponse.SC_NOT_FOUND); + } // Set the response content type diff --git a/source/java/org/alfresco/repo/webdav/PutMethod.java b/source/java/org/alfresco/repo/webdav/PutMethod.java index e696f334b7..8e88ce213e 100644 --- a/source/java/org/alfresco/repo/webdav/PutMethod.java +++ b/source/java/org/alfresco/repo/webdav/PutMethod.java @@ -164,7 +164,14 @@ public class PutMethod extends WebDAVMethod implements ActivityPostProducer } checkNode(contentNodeInfo); - + + // 'Unhide' hidden nodes and behave as though we created them + NodeRef contentNodeRef = contentNodeInfo.getNodeRef(); + if (isHidden(contentNodeRef)) + { + setHidden(contentNodeRef, false); + created = true; + } } catch (FileNotFoundException e) { diff --git a/source/java/org/alfresco/repo/webdav/WebDAVHelper.java b/source/java/org/alfresco/repo/webdav/WebDAVHelper.java index f4610f0d23..e92b37bcf7 100644 --- a/source/java/org/alfresco/repo/webdav/WebDAVHelper.java +++ b/source/java/org/alfresco/repo/webdav/WebDAVHelper.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.StringTokenizer; +import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -34,10 +35,12 @@ import javax.servlet.http.HttpServletResponse; import org.alfresco.jlan.util.IPAddress; import org.alfresco.model.ContentModel; import org.alfresco.repo.lock.LockUtils; +import org.alfresco.repo.model.filefolder.HiddenAspect; import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; @@ -52,7 +55,6 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; import org.alfresco.util.EqualsHelper; import org.alfresco.util.Pair; import org.apache.commons.lang.NotImplementedException; @@ -73,9 +75,13 @@ import org.xml.sax.helpers.AttributesImpl; public class WebDAVHelper { // Constants + + public static final String BEAN_NAME = "webDAVHelper"; + private static final String HTTPS_SCHEME = "https://"; private static final String HTTP_SCHEME = "http://"; + // Path seperator public static final String PathSeperator = "/"; public static final char PathSeperatorChar = '/'; @@ -84,7 +90,7 @@ public class WebDAVHelper // Logging private static Log logger = LogFactory.getLog("org.alfresco.webdav.protocol"); - // Service registry + // Service registry TODO: eliminate this - not dependency injection! private ServiceRegistry m_serviceRegistry; // Services @@ -99,33 +105,30 @@ public class WebDAVHelper private AuthenticationService m_authService; private PermissionService m_permissionService; private TenantService m_tenantService; + private HiddenAspect m_hiddenAspect; + + // pattern is tested against full path after it has been lower cased. + private Pattern m_renameShufflePattern = Pattern.compile("(.*/\\..*)|(.*[a-f0-9]{8}+$)|(.*\\.tmp$)|(.*\\.wbk$)|(.*\\.bak$)|(.*\\~$)"); // Empty XML attribute list - private AttributesImpl m_nullAttribs = new AttributesImpl(); + private final AttributesImpl m_nullAttribs = new AttributesImpl(); private String m_urlPathPrefix; - - /** - * Class constructor - */ - protected WebDAVHelper(String urlPathPrefix, ServiceRegistry serviceRegistry, AuthenticationService authService, TenantService tenantService) - { - m_serviceRegistry = serviceRegistry; - m_nodeService = m_serviceRegistry.getNodeService(); - m_fileFolderService = m_serviceRegistry.getFileFolderService(); - m_searchService = m_serviceRegistry.getSearchService(); - m_namespaceService = m_serviceRegistry.getNamespaceService(); - m_dictionaryService = m_serviceRegistry.getDictionaryService(); - m_mimetypeService = m_serviceRegistry.getMimetypeService(); - m_lockService = (WebDAVLockService)m_serviceRegistry.getService(QName.createQName(NamespaceService.ALFRESCO_URI, WebDAVLockService.BEAN_NAME)); - m_actionService = m_serviceRegistry.getActionService(); - m_permissionService = m_serviceRegistry.getPermissionService(); - m_tenantService = tenantService; - m_authService = authService; - m_urlPathPrefix = urlPathPrefix; + /** + * Set the regular expression that will be applied to filenames during renames + * to detect whether clients are performing a renaming shuffle - common during + * file saving on various clients. + *

+ * + * + * @param renameShufflePattern a regular expression filename match + */ + public void setRenameShufflePattern(Pattern renameShufflePattern) + { + this.m_renameShufflePattern = renameShufflePattern; } - + /** * @return Return the authentication service */ @@ -139,6 +142,7 @@ public class WebDAVHelper */ public final ServiceRegistry getServiceRegistry() { + // TODO: eliminate this - not dependency injection! return m_serviceRegistry; } @@ -211,7 +215,15 @@ public class WebDAVHelper { return m_permissionService; } - + + /** + * @return the hidden aspect bean + */ + public final HiddenAspect getHiddenAspect() + { + return m_hiddenAspect; + } + /** * Retrieve the {@link TenantService} held by the helper. * @@ -230,6 +242,118 @@ public class WebDAVHelper return getServiceRegistry().getCopyService(); } + public void setTenantService(TenantService tenantService) + { + this.m_tenantService = tenantService; + } + + /** + * @param serviceRegistry the service registry + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.m_serviceRegistry = serviceRegistry; + } + + /** + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.m_nodeService = nodeService; + } + + /** + * @param fileFolderService the fileFolder service + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.m_fileFolderService = fileFolderService; + } + + /** + * @param searchService the search service + */ + public void setSearchService(SearchService searchService) + { + this.m_searchService = searchService; + } + + /** + * @param namespaceService the namespace service + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.m_namespaceService = namespaceService; + } + + /** + * @param dictionaryService the dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.m_dictionaryService = dictionaryService; + } + + /** + * @param mimetypeService the mimetype service + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.m_mimetypeService = mimetypeService; + } + + /** + * @param lockService the lock service + */ + public void setLockService(WebDAVLockService lockService) + { + this.m_lockService = lockService; + } + + /** + * @param actionService the action service + */ + public void setActionService(ActionService actionService) + { + this.m_actionService = actionService; + } + + /** + * @param authService the authentication service + */ + public void setAuthenticationService(AuthenticationService authService) + { + this.m_authService = authService; + } + + /** + * @param permissionService the permission service + */ + public void setPermissionService(PermissionService permissionService) + { + this.m_permissionService = permissionService; + } + + /** + * @param hiddenAspect the hiddenAspect to set + */ + public void setHiddenAspect(HiddenAspect hiddenAspect) + { + this.m_hiddenAspect = hiddenAspect; + } + + /** + * Checks a new path in a move operation to detect whether clients are starting a renaming shuffle - common during + * file saving on various clients. + *

+ * ALF-3856, ALF-7079, MNT-181 + */ + public boolean isRenameShuffle(String newPath) + { + return m_renameShufflePattern.matcher(newPath.toLowerCase()).matches(); + } + /** * Split the path into seperate directory path and file name strings. * If the path is not empty, then there will always be an entry for the filename diff --git a/source/java/org/alfresco/repo/webdav/WebDAVHelperTest.java b/source/java/org/alfresco/repo/webdav/WebDAVHelperTest.java index 1d6fe73aa1..4a6ef7df10 100644 --- a/source/java/org/alfresco/repo/webdav/WebDAVHelperTest.java +++ b/source/java/org/alfresco/repo/webdav/WebDAVHelperTest.java @@ -20,13 +20,9 @@ package org.alfresco.repo.webdav; import static org.junit.Assert.assertEquals; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.security.AuthenticationService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.mock.web.MockHttpServletRequest; @@ -39,21 +35,18 @@ import org.springframework.mock.web.MockHttpServletRequest; public class WebDAVHelperTest { private WebDAVHelper davHelper; - private @Mock ServiceRegistry serviceRegistry; - private @Mock AuthenticationService authService; - private @Mock TenantService tenantService; @Before public void setUp() throws Exception { - davHelper = new WebDAVHelper("", serviceRegistry, authService, tenantService); + davHelper = new WebDAVHelper(); } @Test public void canGetUrlPathPrefixWhenExplicitlySet() { // Path prefix explicitly set on helper. - davHelper = new WebDAVHelper("/my/prefix", serviceRegistry, authService, tenantService); + davHelper.setUrlPathPrefix("/my/prefix"); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/my/prefix/folder/filename.txt"); String prefix = davHelper.getUrlPathPrefix(request); assertEquals("/my/prefix/", prefix); @@ -63,7 +56,7 @@ public class WebDAVHelperTest public void canGetUrlPathPrefixFromServletPath() { // Path prefix not explicitly set on helper. - davHelper = new WebDAVHelper("", serviceRegistry, authService, tenantService); + davHelper.setUrlPathPrefix(""); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/before/the-servlet/folder/filename.txt"); // Servlet path will be used to determine path prefix. request.setServletPath("/the-servlet"); diff --git a/source/java/org/alfresco/repo/webdav/WebDAVMethod.java b/source/java/org/alfresco/repo/webdav/WebDAVMethod.java index de926a5bfd..c7ffba6043 100644 --- a/source/java/org/alfresco/repo/webdav/WebDAVMethod.java +++ b/source/java/org/alfresco/repo/webdav/WebDAVMethod.java @@ -46,6 +46,8 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.alfresco.model.ContentModel; +import org.alfresco.repo.model.filefolder.HiddenAspect; +import org.alfresco.repo.model.filefolder.HiddenAspect.Visibility; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.AccessDeniedException; @@ -66,6 +68,8 @@ import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.FileFilterMode; +import org.alfresco.util.FileFilterMode.Client; import org.alfresco.util.TempFileProvider; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -991,6 +995,38 @@ public abstract class WebDAVMethod return ns.toString(); } + + protected void setHidden(NodeRef nodeRef, boolean isHidden) + { + int mask = 0; + boolean allVisible = true; + Visibility webDavVisibility = isHidden ? Visibility.NotVisible : Visibility.Visible; + HiddenAspect hiddenAspect = m_davHelper.getHiddenAspect(); + for (Client client : hiddenAspect.getClients()) + { + Visibility clientVisibility = client == FileFilterMode.getClient() ? webDavVisibility : hiddenAspect + .getVisibility(client, nodeRef); + if (clientVisibility != Visibility.Visible) + { + allVisible = false; + } + mask |= hiddenAspect.getClientVisibilityMask(client, clientVisibility); + } + if (allVisible) + { + getNodeService().removeAspect(nodeRef, ContentModel.ASPECT_HIDDEN); + } + else + { + hiddenAspect.hideNode(nodeRef, mask); + } + } + + protected boolean isHidden(NodeRef nodeRef) + { + return m_davHelper.getHiddenAspect().getVisibility(FileFilterMode.getClient(), nodeRef) != Visibility.Visible; + } + /** * Checks if write operation can be performed on node. * @@ -1098,16 +1134,18 @@ public abstract class WebDAVMethod } else { + // This particular node is not locked so it will not be part of the conditions + if (nodeLockToken == null) + { + return; + } // Node has exclusive lock. Check if conditions contains lock token of the node // If not throw exception for (Condition condition : m_conditions) { - if (nodeLockToken != null) + if (condition.getLockTokensMatch().contains(nodeLockToken)) { - if (condition.getLockTokensMatch().contains(nodeLockToken)) - { - return; - } + return; } } throw new WebDAVServerException(WebDAV.WEBDAV_SC_LOCKED); @@ -1150,9 +1188,9 @@ public abstract class WebDAVMethod // Checks If header conditions. // Each condition can contain check of ETag and check of Lock token. - if (m_conditions == null) + if (m_conditions == null || nodeLockToken == null) { - // No conditions were provided with "If" request header, so check successful + // No conditions were provided with the "If" request header, or this node isn't locked and will not be in the conditions thus the check is successful return; } diff --git a/source/java/org/alfresco/repo/webdav/WebDAVServlet.java b/source/java/org/alfresco/repo/webdav/WebDAVServlet.java index e85b957a9e..741981ba65 100644 --- a/source/java/org/alfresco/repo/webdav/WebDAVServlet.java +++ b/source/java/org/alfresco/repo/webdav/WebDAVServlet.java @@ -41,7 +41,6 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.FileFilterMode; @@ -310,8 +309,6 @@ public class WebDAVServlet extends HttpServlet transactionService = serviceRegistry.getTransactionService(); tenantService = (TenantService) context.getBean("tenantService"); - AuthenticationService authService = (AuthenticationService) context.getBean("authenticationService"); - nodeService = (NodeService) context.getBean("NodeService"); searchService = (SearchService) context.getBean("SearchService"); namespaceService = (NamespaceService) context.getBean("NamespaceService"); @@ -321,7 +318,7 @@ public class WebDAVServlet extends HttpServlet // Collaborator used by WebDAV methods to create activity posts. activityPoster = new ActivityPosterImpl("WebDAV", activityService); - // Create the WebDAV helper + // Get the WebDAV helper m_davHelper = (WebDAVHelper) context.getBean("webDAVHelper"); // Initialize the root node