diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/bulkfilesystemimport/status.get.html.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/bulkfilesystemimport/status.get.html.ftl index c14dbbfe54..f638ca80cc 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/bulkfilesystemimport/status.get.html.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/bulkfilesystemimport/status.get.html.ftl @@ -36,7 +36,6 @@ Bulk Filesystem Import Tool Status Alfresco ${server.edition} v${server.version} - Note: this is unsupported and may be removed from the product in future.

diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/bulkfilesystemimport/ui.get.html.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/bulkfilesystemimport/ui.get.html.ftl index 7873dfcc97..659ef4c20a 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/bulkfilesystemimport/ui.get.html.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/bulkfilesystemimport/ui.get.html.ftl @@ -61,7 +61,6 @@ Bulk Filesystem Import Tool Alfresco ${server.edition} v${server.version} - Note: this is unsupported and may be removed from the product in future.

diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/category/category.put.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/category/category.put.json.ftl index c1649ece35..1bd7b22c2e 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/category/category.put.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/category/category.put.json.ftl @@ -1,6 +1,6 @@ { <#if redirect??>"redirect": "${redirect}", - <#if persistedObject??>"persistedObject": "${persistedObject?replace("\t", "")?string)}", + <#if persistedObject??>"persistedObject": "${persistedObject?replace("\t", "")?string}", "message": "${msg(messageKey)}", "name": "${name}" } \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/solr/aclChangeSets.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/solr/aclChangeSets.get.json.ftl index e2d5c69956..ed3d7c5a55 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/solr/aclChangeSets.get.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/solr/aclChangeSets.get.json.ftl @@ -7,4 +7,11 @@ <#if aclChangeSet_has_next>, ] + <#if maxChangeSetCommitTime??> + ,"maxChangeSetCommitTime": ${maxChangeSetCommitTime?c} + + <#if maxChangeSetId??> + ,"maxChangeSetId": ${maxChangeSetId?c} + + } \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/solr/solr.lib.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/solr/solr.lib.ftl index 49869d6c74..f8022a937d 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/solr/solr.lib.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/solr/solr.lib.ftl @@ -23,7 +23,7 @@ "readers" : [ <#list aclReaders.readers as reader> - ${reader?string} + "${reader?string}" <#if reader_has_next>, ] diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/action/permissions.post.json.js b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/action/permissions.post.json.js index 339af44c79..a91ab67706 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/action/permissions.post.json.js +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/action/permissions.post.json.js @@ -84,6 +84,15 @@ function runAction(p_params) for (var i = 0, j = jsonPermissions.length; i < j; i++) { permissions[jsonPermissions[i].get("group")] = String(jsonPermissions[i].get("role")); + + // It is not allowed for a user to increase access for "All Other Users" on a non-public site. + // This is because the site cannot be accessed until the user has been invited to join the site + if (jsonPermissions[i].get("group") == "GROUP_EVERYONE" && !site.isPublic) + { + result.success = false; + status.setCode(status.STATUS_BAD_REQUEST, "Cannot give permissions to all other permissions on non-public site"); + return; + } } site.setPermissions(fileNode, permissions); break; diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/search/search.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/slingshot/search/search.get.json.ftl index b98f37657b..879e257589 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/search/search.get.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/search/search.get.json.ftl @@ -1,4 +1,3 @@ -<#macro dateFormat date>${date?string("dd MMM yyyy HH:mm:ss 'GMT'Z '('zzz')'")} <#escape x as jsonUtils.encodeJSONString(x)> { "items": @@ -13,7 +12,7 @@ "title": "${item.title}", "description": "${item.description!''}", - "modifiedOn": "<@dateFormat item.modifiedOn />", + "modifiedOn": "${xmldate(item.modifiedOn)}", "modifiedByUser": "${item.modifiedByUser}", "modifiedBy": "${item.modifiedBy}", "size": ${item.size?c}, diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/wiki/pagelist.get.rss.ftl b/config/alfresco/templates/webscripts/org/alfresco/slingshot/wiki/pagelist.get.rss.ftl index dd01274f9f..058cae048a 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/wiki/pagelist.get.rss.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/wiki/pagelist.get.rss.ftl @@ -15,7 +15,7 @@ <#assign page = p.page> ${(page.title!"")?html} - ${shareUrl}/page/site/${siteId}/wiki-page?title=${page.systemName} + ${shareUrl}/page/site/${siteId}/wiki-page?title=${page.systemName?url('UTF-8')} ${page.modifiedAt?string("EEE, dd MMM yyyy HH:mm:ss zzz")} ${node.id} diff --git a/source/java/org/alfresco/repo/web/scripts/bean/ADMRemoteStore.java b/source/java/org/alfresco/repo/web/scripts/bean/ADMRemoteStore.java index c79f4ebbdd..63126ae996 100644 --- a/source/java/org/alfresco/repo/web/scripts/bean/ADMRemoteStore.java +++ b/source/java/org/alfresco/repo/web/scripts/bean/ADMRemoteStore.java @@ -362,8 +362,10 @@ public class ADMRemoteStore extends BaseRemoteStore FileInfo fileInfo = fileFolderService.create( parentFolder.getNodeRef(), encpath.substring(off + 1), ContentModel.TYPE_CONTENT); - contentService.getWriter( - fileInfo.getNodeRef(), ContentModel.PROP_CONTENT, true).putContent(content); + ContentWriter writer = contentService.getWriter( + fileInfo.getNodeRef(), ContentModel.PROP_CONTENT, true); + writer.guessMimetype(fileInfo.getName()); + writer.putContent(content); if (logger.isDebugEnabled()) logger.debug("createDocument: " + fileInfo.toString()); return null; diff --git a/source/java/org/alfresco/repo/web/scripts/solr/AclChangeSetsGet.java b/source/java/org/alfresco/repo/web/scripts/solr/AclChangeSetsGet.java index 75ce6054e4..ce91654385 100644 --- a/source/java/org/alfresco/repo/web/scripts/solr/AclChangeSetsGet.java +++ b/source/java/org/alfresco/repo/web/scripts/solr/AclChangeSetsGet.java @@ -65,6 +65,19 @@ public class AclChangeSetsGet extends DeclarativeWebScript Map model = new HashMap(1, 1.0f); model.put("aclChangeSets", changesets); + Long maxChangeSetCommitTime = solrTrackingComponent.getMaxChangeSetCommitTime(); + if(maxChangeSetCommitTime != null) + { + model.put("maxChangeSetCommitTime", maxChangeSetCommitTime); + } + + Long maxChangeSetId = solrTrackingComponent.getMaxChangeSetId(); + if(maxChangeSetId != null) + { + model.put("maxChangeSetId", maxChangeSetId); + } + + if (logger.isDebugEnabled()) { logger.debug("Result: \n\tRequest: " + req + "\n\tModel: " + model); diff --git a/source/java/org/alfresco/repo/web/scripts/solr/NodeContentGet.java b/source/java/org/alfresco/repo/web/scripts/solr/NodeContentGet.java index 93b855b892..e1c54b7bef 100644 --- a/source/java/org/alfresco/repo/web/scripts/solr/NodeContentGet.java +++ b/source/java/org/alfresco/repo/web/scripts/solr/NodeContentGet.java @@ -117,6 +117,11 @@ public class NodeContentGet extends StreamContent // check If-Modified-Since header and set Last-Modified header as appropriate Date modified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED); + // May be null - if so treat as just changed + if(modified == null) + { + modified = new Date(); + } long modifiedSince = -1; String modifiedSinceStr = req.getHeader("If-Modified-Since"); if(modifiedSinceStr != null) diff --git a/source/java/org/alfresco/repo/webdav/AbstractMoveOrCopyMethod.java b/source/java/org/alfresco/repo/webdav/AbstractMoveOrCopyMethod.java index 0f35708215..16ac636ea7 100644 --- a/source/java/org/alfresco/repo/webdav/AbstractMoveOrCopyMethod.java +++ b/source/java/org/alfresco/repo/webdav/AbstractMoveOrCopyMethod.java @@ -25,6 +25,7 @@ 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 @@ -125,7 +126,8 @@ public abstract class AbstractMoveOrCopyMethod extends HierarchicalMethod 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 (!destInfo.getNodeRef().equals(sourceInfo.getNodeRef()) && !isDestWorkingCopy) + if (!destInfo.getNodeRef().equals(sourceInfo.getNodeRef()) && !isDestWorkingCopy && + !isShuffleOperation(sourceInfo) && !isVersioned(destInfo)) { checkNode(destInfo); @@ -161,4 +163,22 @@ public abstract class AbstractMoveOrCopyMethod extends HierarchicalMethod } } + + 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; + } + + } diff --git a/source/java/org/alfresco/repo/webdav/GetMethod.java b/source/java/org/alfresco/repo/webdav/GetMethod.java index 89a94efb6b..923b108094 100644 --- a/source/java/org/alfresco/repo/webdav/GetMethod.java +++ b/source/java/org/alfresco/repo/webdav/GetMethod.java @@ -178,8 +178,8 @@ public class GetMethod extends WebDAVMethod // is content required if (!m_returnContent) { - // it is a folder and no content is required - throw new WebDAVServerException(HttpServletResponse.SC_BAD_REQUEST); + // ALF-7883 fix, HEAD for collection (see http://www.webdav.org/specs/rfc2518.html#rfc.section.8.4) + return; } // Generate a folder listing diff --git a/source/java/org/alfresco/repo/webdav/LockMethod.java b/source/java/org/alfresco/repo/webdav/LockMethod.java index 340ab384f8..6ad362a217 100644 --- a/source/java/org/alfresco/repo/webdav/LockMethod.java +++ b/source/java/org/alfresco/repo/webdav/LockMethod.java @@ -54,7 +54,7 @@ public class LockMethod extends WebDAVMethod private static Timer timer = new Timer(true); - protected int m_timeoutDuration = WebDAV.TIMEOUT_INFINITY; + protected int m_timeoutDuration = WebDAV.TIMEOUT_24_HOURS; protected LockInfo lockInfo = new LockInfoImpl(); @@ -371,7 +371,6 @@ public class LockMethod extends WebDAVMethod } } - m_response.setHeader(WebDAV.HEADER_LOCK_TOKEN, "<" + WebDAV.makeLockToken(lockNodeInfo.getNodeRef(), userName) + ">"); m_response.setHeader(WebDAV.HEADER_CONTENT_TYPE, WebDAV.XML_CONTENT_TYPE); diff --git a/source/java/org/alfresco/repo/webdav/MoveMethod.java b/source/java/org/alfresco/repo/webdav/MoveMethod.java index bb0047379f..39d4ec5f78 100644 --- a/source/java/org/alfresco/repo/webdav/MoveMethod.java +++ b/source/java/org/alfresco/repo/webdav/MoveMethod.java @@ -87,6 +87,11 @@ public class MoveMethod extends AbstractMoveOrCopyMethod checkNode(sourceFileInfo); + 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)) { @@ -98,12 +103,7 @@ public class MoveMethod extends AbstractMoveOrCopyMethod else if (destFileInfo != null && nodeService.hasAspect(destFileInfo.getNodeRef(), ContentModel.ASPECT_WORKING_COPY)) { // copy only content for working copy destination - 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); + copyOnlyContent(sourceNodeRef, destFileInfo, fileFolderService); } else { @@ -119,4 +119,14 @@ public class MoveMethod extends AbstractMoveOrCopyMethod } } } + + private void copyOnlyContent(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/WebDAV.java b/source/java/org/alfresco/repo/webdav/WebDAV.java index 67352adb7e..99085310e2 100644 --- a/source/java/org/alfresco/repo/webdav/WebDAV.java +++ b/source/java/org/alfresco/repo/webdav/WebDAV.java @@ -61,6 +61,7 @@ public class WebDAV public static final int DEPTH_1 = 1; public static final int DEPTH_INFINITY = -1; public static final short TIMEOUT_INFINITY = -1; + public static final int TIMEOUT_24_HOURS = 86400; // WebDAV HTTP response codes diff --git a/source/java/org/alfresco/repo/webdav/WebDAVHelper.java b/source/java/org/alfresco/repo/webdav/WebDAVHelper.java index 4e6796c52b..5dad8e58a1 100644 --- a/source/java/org/alfresco/repo/webdav/WebDAVHelper.java +++ b/source/java/org/alfresco/repo/webdav/WebDAVHelper.java @@ -37,7 +37,6 @@ 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,6 +51,7 @@ 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.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -90,7 +90,7 @@ public class WebDAVHelper private NamespaceService m_namespaceService; private DictionaryService m_dictionaryService; private MimetypeService m_mimetypeService; - private LockService m_lockService; + private WebDAVLockService m_lockService; private LockStore m_lockStore; private ActionService m_actionService; private AuthenticationService m_authService; @@ -114,7 +114,7 @@ public class WebDAVHelper m_namespaceService = m_serviceRegistry.getNamespaceService(); m_dictionaryService = m_serviceRegistry.getDictionaryService(); m_mimetypeService = m_serviceRegistry.getMimetypeService(); - m_lockService = m_serviceRegistry.getLockService(); + 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; @@ -187,7 +187,7 @@ public class WebDAVHelper /** * @return Return the lock service */ - public final LockService getLockService() + public final WebDAVLockService getLockService() { return m_lockService; } diff --git a/source/java/org/alfresco/repo/webdav/WebDAVLockService.java b/source/java/org/alfresco/repo/webdav/WebDAVLockService.java new file mode 100644 index 0000000000..649c23f428 --- /dev/null +++ b/source/java/org/alfresco/repo/webdav/WebDAVLockService.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2005-2012 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 java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpSession; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.Auditable; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +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.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + *

+ * WebDAVLockService is used to manage file locks for WebDAV and Sharepoint protocol. It ensures a lock never persists + * for more than 24 hours, and also ensures locks are timed out on session timeout. + * + * @author Pavel.Yurkevich + */ +public class WebDAVLockService +{ + public static final String BEAN_NAME = "webDAVLockService"; + + /** The session attribute under which webdav/vti stores its locked documents. */ + private static final String LOCKED_RESOURCES = "_webdavLockedResources"; + + private static Log logger = LogFactory.getLog(WebDAVLockService.class); + + private static ThreadLocal currentSession = new ThreadLocal(); + + private LockService lockService; + private NodeService nodeService; + private TransactionService transactionService; + private CheckOutCheckInService checkOutCheckInService; + + /** + * Set the LockService + * + * @param lockService + */ + public void setLockService(LockService lockService) + { + this.lockService = lockService; + } + + /** + * Set the NodeService + * + * @param nodeService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the TransactionService + * + * @param transactionService + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * Set the CheckOutCheckInService + * + * @param checkOutCheckInService + */ + public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService) + { + this.checkOutCheckInService = checkOutCheckInService; + } + + /** + * Caches current session to the thread local variable + * + * @param currentSession + */ + public static void setCurrentSession(HttpSession session) + { + currentSession.set(session); + } + + @SuppressWarnings("unchecked") + public void sessionDestroyed() + { + HttpSession session = currentSession.get(); + + if (session == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("Couldn't find current session."); + } + return; + } + + // look for locked documents list in http session + final List> lockedResources = (List>) session.getAttribute(LOCKED_RESOURCES); + + if (lockedResources != null && lockedResources.size() > 0) + { + if (logger.isDebugEnabled()) + { + logger.debug("Found " + lockedResources.size() + " locked resources for session: " + session.getId()); + } + + for (Pair lockedResource : lockedResources) + { + String runAsUser = lockedResource.getFirst(); + final NodeRef nodeRef = lockedResource.getSecond(); + + // there are some document that should be forcibly unlocked + AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + return transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // check whether this document still exists in repo + if (nodeService.exists(nodeRef)) + { + if (logger.isDebugEnabled()) + { + logger.debug("Trying to release lock for: " + nodeRef); + } + + // check the lock status of document + LockStatus lockStatus = lockService.getLockStatus(nodeRef); + + // check if document was checked out + boolean hasWorkingCopy = checkOutCheckInService.getWorkingCopy(nodeRef) != null; + boolean isWorkingCopy = nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY); + + // forcibly unlock document if it is still locked and not checked out + if ((lockStatus.equals(LockStatus.LOCKED) || + lockStatus.equals(LockStatus.LOCK_OWNER)) && !hasWorkingCopy && !isWorkingCopy) + { + try + { + // try to unlock it + lockService.unlock(nodeRef); + + if (logger.isDebugEnabled()) + { + logger.debug("Lock was successfully released for: " + + nodeRef); + } + } + catch (Exception e) + { + if (logger.isDebugEnabled()) + { + logger.debug("Unable to unlock " + nodeRef + + " cause: " + e.getMessage()); + } + } + } + else + { + // document is not locked or is checked out + if (logger.isDebugEnabled()) + { + logger.debug("Skip lock releasing for: " + nodeRef + + " as it is not locked or is checked out"); + } + } + } + else + { + // document no longer exists in repo + if (logger.isDebugEnabled()) + { + logger.debug("Skip lock releasing for an unexisting node: " + nodeRef); + } + } + return null; + } + }, transactionService.isReadOnly()); + } + }, runAsUser == null ? AuthenticationUtil.getSystemUserName() : runAsUser); + } + } + else + { + // there are no documents with unexpected lock left on it + if (logger.isDebugEnabled()) + { + logger.debug("No locked resources were found for session: " + session.getId()); + } + } + } + + /** + * Shared method for webdav/vti protocols to lock node. If node is locked for more than 24 hours it is automatically added + * to the current session locked resources list. + * + * @param nodeRef the node to lock + * @param lockType the lock type + * @param timeout the number of seconds before the locks expires + */ + public void lock(NodeRef nodeRef, LockType lockType, int timeout) + { + boolean performSessionBehavior = false; + + // ALF-11777 fix, do not lock node for more than 24 hours (webdav and vti) + if (timeout >= WebDAV.TIMEOUT_24_HOURS || timeout == WebDAV.TIMEOUT_INFINITY) + { + timeout = WebDAV.TIMEOUT_24_HOURS; + performSessionBehavior = true; + } + + this.lockService.lock(nodeRef, lockType, timeout); + + if (logger.isDebugEnabled()) + { + logger.debug(nodeRef + " was locked for " + timeout + " seconds."); + } + + if (performSessionBehavior) + { + HttpSession session = currentSession.get(); + + if (session == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("Couldn't find current session."); + } + return; + } + + storeObjectInSessionList(session, LOCKED_RESOURCES, new Pair(AuthenticationUtil.getRunAsUser(), nodeRef)); + + if (logger.isDebugEnabled()) + { + logger.debug(nodeRef + " was added to the session " + session.getId() + " for post expiration processing."); + } + } + } + + /** + * Shared method for webdav/vti to unlock node. Unlocked node is automatically removed from + * current sessions's locked resources list. + * + * @param nodeRef the node to lock + */ + public void unlock(NodeRef nodeRef) + { + this.lockService.unlock(nodeRef); + + if (logger.isDebugEnabled()) + { + logger.debug(nodeRef + " was unlocked."); + } + + HttpSession session = currentSession.get(); + + if (session == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("Couldn't find current session."); + } + return; + } + + boolean removed = removeObjectFromSessionList(session, LOCKED_RESOURCES, new Pair(AuthenticationUtil.getRunAsUser(), nodeRef)); + + if (removed && logger.isDebugEnabled()) + { + logger.debug(nodeRef + " was removed from the session " + session.getId()); + } + } + + /** + * Gets the lock status for the node reference relative to the current user. + * + * @see LockService#getLockStatus(NodeRef, NodeRef) + * + * @param nodeRef the node reference + * @return the lock status + */ + @Auditable(parameters = {"nodeRef"}) + public LockStatus getLockStatus(NodeRef nodeRef) + { + return this.lockService.getLockStatus(nodeRef); + } + + /** + * Add the given object to the session list that is stored in session under listName attribute + * + * @param session the session + * @param listName the list name (session attribute name) + * @param object the object to store in session list + */ + @SuppressWarnings("unchecked") + private static final void storeObjectInSessionList(HttpSession session, String listName, Object object) + { + List list = null; + + synchronized (session) + { + list = (List) session.getAttribute(listName); + + if (list == null) + { + list = new ArrayList(); + session.setAttribute(listName, list); + } + } + + synchronized (list) + { + if (!list.contains(object)) + { + list.add(object); + } + } + } + + /** + * Removes the given object from the session list that is stored in session under listName attribute + * + * @param session the session + * @param listName the list name (session attribute name) + * @param object the object to store in session list + * + * @return true if session list contained the specified element, otherwise false + */ + @SuppressWarnings("unchecked") + private static final boolean removeObjectFromSessionList(HttpSession session, String listName, Object object) + { + List list = null; + + synchronized (session) + { + list = (List) session.getAttribute(listName); + } + + if (list == null) + { + return false; + } + + synchronized (list) + { + return list.remove(object); + } + } + +} diff --git a/source/java/org/alfresco/repo/webdav/WebDAVMethod.java b/source/java/org/alfresco/repo/webdav/WebDAVMethod.java index 381b784889..95e7ee6914 100644 --- a/source/java/org/alfresco/repo/webdav/WebDAVMethod.java +++ b/source/java/org/alfresco/repo/webdav/WebDAVMethod.java @@ -333,6 +333,9 @@ public abstract class WebDAVMethod WebDAVMethod.this.m_inputStream = null; WebDAVMethod.this.m_reader = null; + // cache current session + WebDAVLockService.setCurrentSession(m_request.getSession()); + executeImpl(); return null; } diff --git a/source/java/org/alfresco/repo/webdav/WebDAVSessionListener.java b/source/java/org/alfresco/repo/webdav/WebDAVSessionListener.java new file mode 100644 index 0000000000..b1d6c57c18 --- /dev/null +++ b/source/java/org/alfresco/repo/webdav/WebDAVSessionListener.java @@ -0,0 +1,88 @@ +/* + * 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.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + *

WebDAVSessionListener is used to forcibly unlock documents that were + * persistently locked during user's session and were not unlocked because of some extraordinary + * situations such as network connection lost. Was introduced in ALF-11777 jira issue. + *

+ * + * @author Pavel.Yurkevich + * + */ +public class WebDAVSessionListener implements HttpSessionListener, ServletContextListener +{ + private static Log logger = LogFactory.getLog(WebDAVSessionListener.class); + private WebDAVLockService webDAVLockService; + + /** + * @param webDAVLockService + * the webDAVLockService to set + */ + public void setWebDAVLockService(WebDAVLockService webDAVLockService) + { + this.webDAVLockService = webDAVLockService; + } + + @Override + public void sessionCreated(HttpSessionEvent hse) + { + if (logger.isDebugEnabled()) + { + logger.debug("Session created " + hse.getSession().getId()); + } + } + + @SuppressWarnings("unchecked") + @Override + public void sessionDestroyed(HttpSessionEvent hse) + { + WebDAVLockService.setCurrentSession(hse.getSession()); + webDAVLockService.sessionDestroyed(); + + if (logger.isDebugEnabled()) + { + logger.debug("Session destroyed " + hse.getSession().getId()); + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) + { + } + + @Override + public void contextInitialized(ServletContextEvent sce) + { + ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(sce.getServletContext()); + this.webDAVLockService = (WebDAVLockService)context.getBean(WebDAVLockService.BEAN_NAME); + } +}