diff --git a/source/java/org/alfresco/repo/webdav/LockInfo.java b/source/java/org/alfresco/repo/webdav/LockInfo.java index d8db2db685..d1a118e98d 100644 --- a/source/java/org/alfresco/repo/webdav/LockInfo.java +++ b/source/java/org/alfresco/repo/webdav/LockInfo.java @@ -18,6 +18,8 @@ */ package org.alfresco.repo.webdav; +import java.io.Serializable; +import java.util.Date; import java.util.HashSet; import java.util.Set; @@ -27,10 +29,12 @@ import java.util.Set; * @author Ivan Rybnikov * */ -public class LockInfo +public final class LockInfo implements Serializable { + private static final long serialVersionUID = 1L; + // Exclusive lock token - private String token = null; + private String exclusiveLockToken = null; // Lock scope private String scope = null; @@ -38,15 +42,15 @@ public class LockInfo // Lock depth private String depth = null; - // If lock is shared - private boolean shared = false; - // Shared lock tokens - private Set sharedLockTokens = null; - - // Shared lock token separator - private static final String SHARED_LOCK_TOKEN_SEPARATOR = ","; + private final Set sharedLockTokens = new HashSet(3); + // User name of the lock's owner + private String owner; + + // When does the lock expire? + private Date expires; + /** * Default constructor * @@ -64,7 +68,7 @@ public class LockInfo */ public LockInfo(String token, String scope, String depth) { - this.token = token; + this.exclusiveLockToken = token; this.scope = scope; this.depth = depth; } @@ -76,11 +80,7 @@ public class LockInfo */ public boolean isLocked() { - if (token != null || (sharedLockTokens != null && !sharedLockTokens.isEmpty())) - { - return true; - } - return false; + return (isExclusive() || isShared()); } /** @@ -88,18 +88,20 @@ public class LockInfo * * @param token Lock token */ - public void setToken(String token) + public void setExclusiveLockToken(String token) { - this.token = token; + this.exclusiveLockToken = token; } /** - * Getter for exclusive lock token - * @return + * Getter for exclusive lock token. + * + * @return String */ - public String getToken() + public String getExclusiveLockToken() { - return token; + checkLockState(); + return exclusiveLockToken; } /** @@ -143,110 +145,48 @@ public class LockInfo } /** - * Transforms shared lock tokens string to list. - * - * @param sharedLockTokens String contains all node's shared lock tokens - * divided with SHARED_LOCK_TOKEN_SEPARATOR value. - * @return List of shared lock tokens - */ - public static Set parseSharedLockTokens(String sharedLockTokens) - { - if (sharedLockTokens == null) - { - return null; - } - - String[] sl = sharedLockTokens.split(SHARED_LOCK_TOKEN_SEPARATOR); - Set result = new HashSet(sl.length * 2); - for (int i = 0; i < sl.length; i++) - { - result.add(sl[i]); - } - - return result; - } - - /** - * Getter for sharedLockTokens list + * Getter for sharedLockTokens list. * * @return LinkedList */ public Set getSharedLockTokens() { + checkLockState(); return sharedLockTokens; } /** - * Setter for sharedLockTokens list + * Setter for sharedLockTokens list. * * @param sharedLockTokens */ public void setSharedLockTokens(Set sharedLockTokens) { - this.sharedLockTokens = sharedLockTokens; + this.sharedLockTokens.clear(); + this.sharedLockTokens.addAll(sharedLockTokens); } /** - * Adds new shared lock token to sharedLockTokens list + * Adds new shared lock token to sharedLockTokens list. * - * @param token new token + * @param token The token to add. */ public void addSharedLockToken(String token) { - if (sharedLockTokens == null) - { - sharedLockTokens = new HashSet(3); - } sharedLockTokens.add(token); } /** - * Transforms list of shared locks to string. - * Lock tokens separated with SHARED_LOCK_TOKEN_SEPARATOR value. + * Is it a shared lock? * - * @param lockTokens list of shared locks - * @return String - */ - public static String makeSharedLockTokensString(Set lockTokens) - { - StringBuilder str = new StringBuilder(); - - boolean first = true; - for (String token : lockTokens) - { - if (!first) - { - str.append(SHARED_LOCK_TOKEN_SEPARATOR); - } - else - { - first = false; - } - str.append(token); - } - return str.toString(); - } - - /** - * Setter for shared property - * - * @param shared - */ - public void setShared(boolean shared) - { - this.shared = shared; - } - - /** - * Returns true is lock is shared - * - * @return boolean + * @return true if shared. */ public boolean isShared() { - return shared; + return (!sharedLockTokens.isEmpty()); } + /** * Return the lock info as a string * @@ -256,20 +196,113 @@ public class LockInfo { StringBuilder str = new StringBuilder(); - str.append("["); + str.append("LockInfo["); - str.append("token="); - str.append(getToken()); - str.append(",scope="); + str.append("exclusiveLockToken="); + str.append(getExclusiveLockToken()); + str.append(", scope="); str.append(getScope()); - str.append(",depth="); + str.append(", depth="); str.append(getDepth()); - str.append(",shared locks="); + str.append(", sharedLockTokens="); str.append(getSharedLockTokens()); - + str.append(", owner="); + str.append(owner); + str.append(", expires="); + str.append(expires); + str.append("]"); return str.toString(); } + /** + * Whether this lock has expired. If no expiry is set (i.e. expires is null) + * then false is always returned. + * + * @return true if expired. + */ + public boolean isExpired() + { + if (expires == null) + { + return false; + } + Date now = new Date(); + return now.after(expires); + } + + /** + * Is it an exclusive lock? + * + * @return true if exclusive. + */ + public boolean isExclusive() + { + return (exclusiveLockToken != null && exclusiveLockToken.length() > 0); + } + + /** + * Who owns the lock? + * + * @return the owner + */ + public String getOwner() + { + return owner; + } + + /** + * Set the username of who owns the lock. + * + * @param owner Owner's username + */ + public void setOwner(String owner) + { + this.owner = owner; + } + + /** + * Set the expiry date/time for this lock. Set to null for never expires. + * + * @param expires the expires to set + */ + public void setExpires(Date expires) + { + this.expires = expires; + } + + /** + * Retrieve the expiry date/time for this lock, or null if it never expires. + * + * @return the expires + */ + public Date getExpires() + { + return expires; + } + + /** + * Sanity check the state of this LockInfo. + */ + private void checkLockState() + { + if (isShared() && isExclusive()) + { + throw new IllegalStateException("Lock cannot be both shared and exclusive: " + toString()); + } + } + + /** + * Sets the expiry date/time to lockTimeout seconds into the future. + * + * @param lockTimeout + */ + public void setTimeoutSeconds(int lockTimeout) + { + int timeoutMillis = (lockTimeout * 60 * 1000); + Date now = new Date(); + Date nextExpiry = new Date(now.getTime() + timeoutMillis); + setExpires(nextExpiry); + } } diff --git a/source/java/org/alfresco/repo/webdav/LockMethod.java b/source/java/org/alfresco/repo/webdav/LockMethod.java index 3e2597d166..8552df1e2b 100644 --- a/source/java/org/alfresco/repo/webdav/LockMethod.java +++ b/source/java/org/alfresco/repo/webdav/LockMethod.java @@ -18,6 +18,7 @@ */ package org.alfresco.repo.webdav; +import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -27,12 +28,9 @@ import java.util.TimerTask; import javax.servlet.http.HttpServletResponse; import org.alfresco.model.ContentModel; -import org.alfresco.model.WebDAVModel; 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.cmr.lock.LockService; -import org.alfresco.service.cmr.lock.LockType; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileFolderUtil; import org.alfresco.service.cmr.model.FileInfo; @@ -390,40 +388,34 @@ public class LockMethod extends WebDAVMethod */ protected final void createLock(FileInfo lockNode, String userName) throws WebDAVServerException { - LockService lockService = getLockService(); - // Create Lock token lockToken = WebDAV.makeLockToken(lockNode.getNodeRef(), userName); - if (this.createExclusive) + if (createExclusive) { // Lock the node - lockService.lock(lockNode.getNodeRef(), LockType.WRITE_LOCK, getLockTimeout()); - - // update local cache (will be read back when generating the response) - lockNode.getProperties().put(ContentModel.PROP_LOCK_OWNER, userName); - // ALF-3681, we should also cache the expiryDate for correct response generation - lockNode.getProperties().put(ContentModel.PROP_EXPIRY_DATE, - getNodeService().getProperty(lockNode.getNodeRef(), ContentModel.PROP_EXPIRY_DATE)); - - //this.lockInfo.setToken(lockToken); - getNodeService().setProperty(lockNode.getNodeRef(), WebDAVModel.PROP_OPAQUE_LOCK_TOKEN, lockToken); + lockInfo.setTimeoutSeconds(getLockTimeout()); + lockInfo.setExclusiveLockToken(lockToken); } else { - this.lockInfo.addSharedLockToken(lockToken); - String sharedLockTokens = LockInfo.makeSharedLockTokensString(this.lockInfo.getSharedLockTokens()); - getNodeService().setProperty(lockNode.getNodeRef(), WebDAVModel.PROP_SHARED_LOCK_TOKENS, sharedLockTokens); - + lockInfo.addSharedLockToken(lockToken); } // Store lock depth - getNodeService().setProperty(lockNode.getNodeRef(), WebDAVModel.PROP_LOCK_DEPTH, WebDAV.getDepthName(m_depth)); - + lockInfo.setDepth(WebDAV.getDepthName(m_depth)); // Store lock scope (shared/exclusive) - getNodeService().setProperty(lockNode.getNodeRef(), WebDAVModel.PROP_LOCK_SCOPE, this.createExclusive ? WebDAV.XML_EXCLUSIVE : WebDAV.XML_SHARED); - - logger.debug("Properties of the " + lockNode + " was changed"); + String scope = createExclusive ? WebDAV.XML_EXCLUSIVE : WebDAV.XML_SHARED; + lockInfo.setScope(scope); + // Store the owner of this lock + lockInfo.setOwner(userName); + // Lock the node + getLockStore().put(lockNode.getNodeRef(), lockInfo); + + if (logger.isDebugEnabled()) + { + logger.debug("Locked node " + lockNode + ": " + lockInfo); + } } /** @@ -438,7 +430,10 @@ public class LockMethod extends WebDAVMethod if (this.createExclusive) { // Update the expiry for the lock - getLockService().lock(lockNode.getNodeRef(), LockType.WRITE_LOCK, getLockTimeout()); + if (lockInfo.getExpires() != null) + { + lockInfo.setTimeoutSeconds(getLockTimeout()); + } } } @@ -461,10 +456,10 @@ public class LockMethod extends WebDAVMethod // In case of lock refreshing take the scope from previously stored lock scope = this.lockInfo.getScope(); // Output refreshed lock - lt = this.lockInfo.getToken(); + lt = this.lockInfo.getExclusiveLockToken(); } - String owner = (String) lockNodeInfo.getProperties().get(ContentModel.PROP_LOCK_OWNER); - + String owner = lockInfo.getOwner(); + XMLWriter xml = createXMLWriter(); xml.startDocument(); @@ -473,7 +468,8 @@ public class LockMethod extends WebDAVMethod xml.startElement(WebDAV.DAV_NS, WebDAV.XML_PROP + nsdec, WebDAV.XML_NS_PROP + nsdec, getDAVHelper().getNullAttributes()); // Output the lock details - generateLockDiscoveryXML(xml, lockNodeInfo, false, scope, WebDAV.getDepthName(m_depth), lt, owner); + Date expiry = lockInfo.getExpires(); + generateLockDiscoveryXML(xml, lockNodeInfo, false, scope, WebDAV.getDepthName(m_depth), lt, owner, expiry); // Close off the XML xml.endElement(WebDAV.DAV_NS, WebDAV.XML_PROP, WebDAV.XML_NS_PROP); diff --git a/source/java/org/alfresco/repo/webdav/LockStore.java b/source/java/org/alfresco/repo/webdav/LockStore.java new file mode 100644 index 0000000000..0437b61988 --- /dev/null +++ b/source/java/org/alfresco/repo/webdav/LockStore.java @@ -0,0 +1,58 @@ +/* + * 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 org.alfresco.service.cmr.repository.NodeRef; + +/** + * Provides storage for WebDAV {@link LockInfo lock information}. + *

+ * Note: the existence of LockInfo does NOT mean that a node is necessarily locked. It may have timed-out, + * been unlocked, or be left in an invalid state for some reason. The LockInfo is a record of a requested lock - + * the actual values should be examined as necessary. + *

+ * Implementations of this interface should be fast, ideally an in-memory map. Implementations should also be thread- + * and cluster-safe. + * + * @author Matt Ward + */ +public interface LockStore +{ + /** + * Provide LockInfo about a specific node to the LockStore. + * + * @param nodeToLock + * @param lockInfo + */ + void put(NodeRef nodeToLock, LockInfo lockInfo); + + /** + * Retrieves LockInfo for as given nodeRef. The LockInfo may specify that a node is + * NOT locked, so the LockInfo should always be checked for validity. + *

+ * The presence of LockInfo does not imply that a node is locked. + * + * @param nodeRef + * @return + */ + LockInfo get(NodeRef nodeRef); + + + void remove(NodeRef nodeRef); +} diff --git a/source/java/org/alfresco/repo/webdav/LockStoreFactory.java b/source/java/org/alfresco/repo/webdav/LockStoreFactory.java new file mode 100644 index 0000000000..207ede744e --- /dev/null +++ b/source/java/org/alfresco/repo/webdav/LockStoreFactory.java @@ -0,0 +1,30 @@ +/* + * 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; + + +/** + * Factory to create {@link LockStore} instances. + * + * @author Matt Ward + */ +public interface LockStoreFactory +{ + LockStore getLockStore(); +} diff --git a/source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java b/source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java new file mode 100644 index 0000000000..59521b07d1 --- /dev/null +++ b/source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java @@ -0,0 +1,54 @@ +/* + * 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.concurrent.ConcurrentMap; + +import org.alfresco.service.cmr.repository.NodeRef; + +import com.hazelcast.core.HazelcastInstance; + + +/** + * Default implementation of the {@link LockStoreFactory} interface. Creates {@link LockStore}s + * backed by a Hazelcast distributed {@link java.util.Map Map}. + * + * @see LockStoreFactory + * @see LockStoreImpl + * @author Matt Ward + */ +public class LockStoreFactoryImpl implements LockStoreFactory +{ + private HazelcastInstance hazelcast; + + @Override + public LockStore getLockStore() + { + ConcurrentMap map = hazelcast.getMap("webdav-locks"); + return new LockStoreImpl(map); + } + + /** + * @param hazelcast the hazelcast to set + */ + public void setHazelcast(HazelcastInstance hazelcast) + { + this.hazelcast = hazelcast; + } +} diff --git a/source/java/org/alfresco/repo/webdav/LockStoreImpl.java b/source/java/org/alfresco/repo/webdav/LockStoreImpl.java new file mode 100644 index 0000000000..f21ee18f6d --- /dev/null +++ b/source/java/org/alfresco/repo/webdav/LockStoreImpl.java @@ -0,0 +1,59 @@ +/* + * 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.concurrent.ConcurrentMap; + +import org.alfresco.service.cmr.repository.NodeRef; + + +/** + * The default {@link LockStore} implementation. This is based upon a ConcurrentMap intended to be supplied by + * Hazelcast (or a similar, distributed data structure library). + * + * @see LockStore + * @author Matt Ward + */ +public class LockStoreImpl implements LockStore +{ + private final ConcurrentMap lockInfoMap; + + public LockStoreImpl(ConcurrentMap lockInfoMap) + { + this.lockInfoMap = lockInfoMap; + } + + @Override + public void put(NodeRef nodeToLock, LockInfo lockInfo) + { + lockInfoMap.put(nodeToLock, lockInfo); + } + + @Override + public LockInfo get(NodeRef nodeRef) + { + return lockInfoMap.get(nodeRef); + } + + @Override + public void remove(NodeRef nodeRef) + { + lockInfoMap.remove(nodeRef); + } +} diff --git a/source/java/org/alfresco/repo/webdav/PutMethod.java b/source/java/org/alfresco/repo/webdav/PutMethod.java index a3f344ffad..a5cc1612a3 100644 --- a/source/java/org/alfresco/repo/webdav/PutMethod.java +++ b/source/java/org/alfresco/repo/webdav/PutMethod.java @@ -24,10 +24,9 @@ import javax.servlet.http.HttpServletResponse; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.executer.ContentMetadataExtracter; -import org.alfresco.service.cmr.action.Action; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.lock.LockStatus; +import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.model.FileExistsException; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; @@ -44,7 +43,6 @@ import org.springframework.dao.ConcurrencyFailureException; public class PutMethod extends WebDAVMethod { // Request parameters - private String m_strLockToken = null; private String m_strContentType = null; private boolean m_expectHeaderPresent = false; @@ -189,12 +187,17 @@ public class PutMethod extends WebDAVMethod } } - LockStatus lockSts = getLockService().getLockStatus(contentNodeInfo.getNodeRef()); String userName = getDAVHelper().getAuthenticationService().getCurrentUserName(); - String owner = (String) getNodeService().getProperty(contentNodeInfo.getNodeRef(), ContentModel.PROP_LOCK_OWNER); - - if (lockSts == LockStatus.LOCKED || (lockSts == LockStatus.LOCK_OWNER && !userName.equals(owner))) + LockInfo lockInfo = getLockStore().get(contentNodeInfo.getNodeRef()); + + if (lockInfo != null && lockInfo.isLocked() && !lockInfo.getOwner().equals(userName)) { + if (logger.isDebugEnabled()) + { + String path = getPath(); + String owner = lockInfo.getOwner(); + logger.debug("Node locked: path=["+path+"], owner=["+owner+"], current user=["+userName+"]"); + } // Indicate that the resource is locked throw new WebDAVServerException(WebDAV.WEBDAV_SC_LOCKED); } diff --git a/source/java/org/alfresco/repo/webdav/UnlockMethod.java b/source/java/org/alfresco/repo/webdav/UnlockMethod.java index c59a53b4ee..f406bf4c84 100644 --- a/source/java/org/alfresco/repo/webdav/UnlockMethod.java +++ b/source/java/org/alfresco/repo/webdav/UnlockMethod.java @@ -23,13 +23,9 @@ import java.util.Set; import javax.servlet.http.HttpServletResponse; import org.alfresco.model.ContentModel; -import org.alfresco.model.WebDAVModel; -import org.alfresco.service.cmr.lock.LockService; -import org.alfresco.service.cmr.lock.LockStatus; 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 UNLOCK method @@ -126,37 +122,42 @@ public class UnlockMethod extends WebDAVMethod } // Parse the lock token - String[] lockInfo = WebDAV.parseLockToken(getLockToken()); - if (lockInfo == null) + String[] lockInfoFromRequest = WebDAV.parseLockToken(getLockToken()); + if (lockInfoFromRequest == null) { // Bad lock token throw new WebDAVServerException(HttpServletResponse.SC_PRECONDITION_FAILED); } - // Get the lock status for the node - LockService lockService = getDAVHelper().getLockService(); - NodeService nodeService = getNodeService(); - // String nodeId = lockInfo[0]; - // String userName = lockInfo[1]; - - NodeRef nodeRef = lockNodeInfo.getNodeRef(); - LockStatus lockSts = lockService.getLockStatus(nodeRef); - if (lockSts == LockStatus.LOCK_OWNER) + NodeRef nodeRef = lockNodeInfo.getNodeRef(); + LockInfo lockInfo = getLockStore().get(nodeRef); + + if (lockInfo == null || !lockInfo.isLocked()) { - if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY)) - lockService.unlock(nodeRef); - nodeService.removeProperty(nodeRef, WebDAVModel.PROP_OPAQUE_LOCK_TOKEN); - nodeService.removeProperty(nodeRef, WebDAVModel.PROP_LOCK_DEPTH); - nodeService.removeProperty(nodeRef, WebDAVModel.PROP_LOCK_SCOPE); - - // Return the cm:lockable aspect to working copy (ALF-4479, ALF-7079) - if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY)) + if (logger.isDebugEnabled()) { - nodeService.addAspect(nodeRef, ContentModel.ASPECT_LOCKABLE, null); + logger.debug("Unlock token=" + getLockToken() + " Not locked"); } + // Node is not locked + throw new WebDAVServerException(HttpServletResponse.SC_PRECONDITION_FAILED); + } + else if (lockInfo.isExpired()) + { + if (logger.isDebugEnabled()) + { + logger.debug("Unlock token=" + getLockToken() + " Lock expired"); + } + // Return a success status + m_response.setStatus(HttpServletResponse.SC_NO_CONTENT); + removeNoContentAspect(nodeRef); + } + else if (lockInfo.isExclusive() /* && user is lock-owner */) + { + getLockStore().remove(nodeRef); // Indicate that the unlock was successful m_response.setStatus(HttpServletResponse.SC_NO_CONTENT); + removeNoContentAspect(nodeRef); // DEBUG @@ -165,57 +166,27 @@ public class UnlockMethod extends WebDAVMethod logger.debug("Unlock token=" + getLockToken() + " Successful"); } } - else if (lockSts == LockStatus.NO_LOCK) + else if (lockInfo.isShared()) { - String sharedLocks = (String) nodeService.getProperty(nodeRef, WebDAVModel.PROP_SHARED_LOCK_TOKENS); - if (sharedLocks != null) + Set sharedLocks = lockInfo.getSharedLockTokens(); + if (sharedLocks.contains(m_strLockToken)) { - Set locks = LockInfo.parseSharedLockTokens(sharedLocks); - - if (locks != null && locks.contains(m_strLockToken)) - { - locks.remove(m_strLockToken); - nodeService.setProperty(nodeRef, WebDAVModel.PROP_SHARED_LOCK_TOKENS, LockInfo.makeSharedLockTokensString(locks)); + sharedLocks.remove(m_strLockToken); - // Indicate that the unlock was successful - m_response.setStatus(HttpServletResponse.SC_NO_CONTENT); - removeNoContentAspect(nodeRef); + // Indicate that the unlock was successful + m_response.setStatus(HttpServletResponse.SC_NO_CONTENT); + removeNoContentAspect(nodeRef); - // DEBUG - if (logger.isDebugEnabled()) - { - logger.debug("Unlock token=" + getLockToken() + " Successful"); - } - } - } - else - { // DEBUG if (logger.isDebugEnabled()) - logger.debug("Unlock token=" + getLockToken() + " Not locked"); - - // Node is not locked - throw new WebDAVServerException(HttpServletResponse.SC_PRECONDITION_FAILED); + { + logger.debug("Unlock token=" + getLockToken() + " Successful"); + } } } - else if (lockSts == LockStatus.LOCKED) + else { - // DEBUG - if (logger.isDebugEnabled()) - logger.debug("Unlock token=" + getLockToken() + " Not lock owner"); - - // Node is locked but not by this user - throw new WebDAVServerException(HttpServletResponse.SC_PRECONDITION_FAILED); - } - else if (lockSts == LockStatus.LOCK_EXPIRED) - { - // DEBUG - if (logger.isDebugEnabled()) - logger.debug("Unlock token=" + getLockToken() + " Lock expired"); - - // Return a success status - m_response.setStatus(HttpServletResponse.SC_NO_CONTENT); - removeNoContentAspect(nodeRef); + throw new IllegalStateException("Invalid LockInfo state: " + lockInfo); } } diff --git a/source/java/org/alfresco/repo/webdav/WebDAVHelper.java b/source/java/org/alfresco/repo/webdav/WebDAVHelper.java index 1f1dea12e7..ce1e90ad99 100644 --- a/source/java/org/alfresco/repo/webdav/WebDAVHelper.java +++ b/source/java/org/alfresco/repo/webdav/WebDAVHelper.java @@ -77,6 +77,7 @@ public class WebDAVHelper private DictionaryService m_dictionaryService; private MimetypeService m_mimetypeService; private LockService m_lockService; + private LockStore m_lockStore; private ActionService m_actionService; private AuthenticationService m_authService; private PermissionService m_permissionService; @@ -88,7 +89,7 @@ public class WebDAVHelper /** * Class constructor */ - protected WebDAVHelper(ServiceRegistry serviceRegistry, AuthenticationService authService) + protected WebDAVHelper(ServiceRegistry serviceRegistry, LockStore lockStore, AuthenticationService authService) { m_serviceRegistry = serviceRegistry; @@ -103,6 +104,8 @@ public class WebDAVHelper m_permissionService = m_serviceRegistry.getPermissionService(); m_authService = authService; + + m_lockStore = lockStore; } /** @@ -173,6 +176,14 @@ public class WebDAVHelper { return m_lockService; } + + /** + * @return Return the {@link LockStore lock store}. + */ + public final LockStore getLockStore() + { + return m_lockStore; + } /** * @return Return the action service diff --git a/source/java/org/alfresco/repo/webdav/WebDAVMethod.java b/source/java/org/alfresco/repo/webdav/WebDAVMethod.java index ba76b51724..412ff46ba1 100644 --- a/source/java/org/alfresco/repo/webdav/WebDAVMethod.java +++ b/source/java/org/alfresco/repo/webdav/WebDAVMethod.java @@ -25,15 +25,14 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import java.util.regex.Pattern; import javax.servlet.ServletInputStream; @@ -45,16 +44,12 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.alfresco.model.ContentModel; -import org.alfresco.model.WebDAVModel; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.ActionService; -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.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; @@ -654,13 +649,13 @@ public abstract class WebDAVMethod } /** - * Convenience method to return the lock service + * Retrieve the (WebDAV protocol-level) {@link LockStore lock store}. * - * @return LockService + * @return LockStore */ - protected final LockService getLockService() + protected final LockStore getLockStore() { - return m_davHelper.getLockService(); + return m_davHelper.getLockStore(); } /** @@ -747,16 +742,19 @@ public abstract class WebDAVMethod return writer; } - /** + /** * Generates the lock discovery XML response * * @param xml XMLWriter * @param lockNode NodeRef + * @param lockInfo */ protected void generateLockDiscoveryXML(XMLWriter xml, FileInfo lockNodeInfo, LockInfo lockInfo) throws Exception { - String owner = (String) lockNodeInfo.getProperties().get(ContentModel.PROP_LOCK_OWNER); - generateLockDiscoveryXML(xml, lockNodeInfo, false, lockInfo.getScope(), lockInfo.getDepth(), WebDAV.makeLockToken(lockNodeInfo.getNodeRef(), owner), owner); + String owner = lockInfo.getOwner(); + Date expiry = lockInfo.getExpires(); + generateLockDiscoveryXML(xml, lockNodeInfo, false, lockInfo.getScope(), lockInfo.getDepth(), + WebDAV.makeLockToken(lockNodeInfo.getNodeRef(), owner), owner, expiry); } /** @@ -769,17 +767,15 @@ public abstract class WebDAVMethod * @param depth String lock depth * @param lToken String locktoken * @param owner String lock owner - * + * @param expiryDate the date/time the lock should expire */ - protected void generateLockDiscoveryXML(XMLWriter xml, FileInfo lockNodeInfo, boolean emptyNamespace, String scope, String depth, String lToken, String owner) throws Exception + protected void generateLockDiscoveryXML(XMLWriter xml, FileInfo lockNodeInfo, boolean emptyNamespace, + String scope, String depth, String lToken, String owner, Date expiryDate) throws Exception { Attributes nullAttr= getDAVHelper().getNullAttributes(); String ns = emptyNamespace ? "" : WebDAV.DAV_NS; if (lockNodeInfo != null) { - // Get the lock details - Date expiryDate = (Date) lockNodeInfo.getProperties().get(ContentModel.PROP_EXPIRY_DATE); - // Output the XML response xml.startElement(ns, WebDAV.XML_LOCK_DISCOVERY, emptyNamespace ? WebDAV.XML_LOCK_DISCOVERY : WebDAV.XML_NS_LOCK_DISCOVERY, nullAttr); @@ -882,15 +878,22 @@ public abstract class WebDAVMethod if (m_conditions == null) { - if (nodeLockInfo.getToken() == null) + if (!nodeLockInfo.isLocked()) { - CheckOutCheckInService checkOutCheckInService = m_davHelper.getServiceRegistry().getCheckOutCheckInService(); - if (nodeLockInfo.getSharedLockTokens() == null && checkOutCheckInService.getWorkingCopy(fileInfo.getNodeRef()) == null) + // Not locked + return nodeLockInfo; + } + + if (nodeLockInfo.isShared()) + { + if (nodeLockInfo.getSharedLockTokens().isEmpty()) { + // Although flagged as shared - no shared locks. return nodeLockInfo; } if (!ignoreShared) { + // Shared locks exist throw new WebDAVServerException(WebDAV.WEBDAV_SC_LOCKED); } } @@ -900,15 +903,16 @@ public abstract class WebDAVMethod if (m_userAgent != null && m_userAgent.equals(WebDAV.AGENT_MICROSOFT_DATA_ACCESS_INTERNET_PUBLISHING_PROVIDER_DAV)) { String currentUser = getAuthenticationService().getCurrentUserName(); - Serializable lockOwner = fileInfo.getProperties().get(ContentModel.PROP_LOCK_OWNER); - + String lockOwner = nodeLockInfo.getOwner(); if (lockOwner.equals(currentUser)) { + // OK to write - lock is owned by current user. return nodeLockInfo; } } else { + // Exclusive lock, owned by someone else throw new WebDAVServerException(WebDAV.WEBDAV_SC_LOCKED); } } @@ -920,7 +924,7 @@ public abstract class WebDAVMethod // but request must fail with 423 Locked response because node is locked. // 2. Check if ANY of the conditions in If header true. checkLockToken(nodeLockInfo, ignoreShared, lockMethod); - checkConditions(nodeLockInfo.getToken(), nodeETag); + checkConditions(nodeLockInfo.getExclusiveLockToken(), nodeETag); return nodeLockInfo; } @@ -949,7 +953,7 @@ public abstract class WebDAVMethod */ private void checkLockToken(LockInfo lockInfo, boolean ignoreShared, boolean lockMethod) throws WebDAVServerException { - String nodeLockToken = lockInfo.getToken(); + String nodeLockToken = lockInfo.getExclusiveLockToken(); Set sharedLockTokens = lockInfo.getSharedLockTokens(); if (m_conditions != null) @@ -959,7 +963,7 @@ public abstract class WebDAVMethod { // Node has shared lock. Check if conditions contains lock token of the node. // If not throw exception - if (sharedLockTokens != null) + if (!sharedLockTokens.isEmpty()) { if (!ignoreShared) { @@ -1096,44 +1100,15 @@ public abstract class WebDAVMethod private LockInfo getNodeLockInfoImpl(FileInfo nodeInfo) { // Check if node is locked directly. - LockInfo lockInfo = getNodeLockInfoDirect(nodeInfo); if (lockInfo != null) { return lockInfo; } - else - { - // No has no exclusive lock but can be locked with shared lock - String sharedLocks = (String) nodeInfo.getProperties().get(WebDAVModel.PROP_SHARED_LOCK_TOKENS); - if (sharedLocks != null) - { - // Get lock depth - String depth = (String) nodeInfo.getProperties().get(WebDAVModel.PROP_LOCK_DEPTH); - //Get lock scope - String scope = (String) nodeInfo.getProperties().get(WebDAVModel.PROP_LOCK_SCOPE); - - // Node has it's own Lock token. - // Store lock information to the lockInfo object - - lockInfo = new LockInfo(); - - lockInfo.setDepth(depth); - lockInfo.setScope(scope); - lockInfo.setSharedLockTokens(LockInfo.parseSharedLockTokens(sharedLocks)); - lockInfo.setShared(true); - - return lockInfo; - } - } - - // Node isn't locked directly and has no it's own Lock token. - // Try to search indirect lock. + // Node isn't locked directly, try to search for an indirect lock. NodeService nodeService = getNodeService(); - - NodeRef nodeRef = nodeInfo.getNodeRef(); - NodeRef node = nodeRef; + NodeRef node = nodeInfo.getNodeRef(); while (true) { @@ -1175,41 +1150,13 @@ public abstract class WebDAVMethod { return lockInfo; } - else - { - // TODO - I assume this should be parent not nodeRef (see ALF-6224) ? - - lockInfo = new LockInfo(); - - // Node has no exclusive lock but can be locked with shared lock - String sharedLocks = (String) nodeService.getProperty(parent, WebDAVModel.PROP_SHARED_LOCK_TOKENS); - if (sharedLocks != null) - { - // Check node lock depth. - // If depth is WebDAV.INFINITY then return this node's Lock token. - String depth = (String) nodeService.getProperty(parent, WebDAVModel.PROP_LOCK_DEPTH); - if (WebDAV.INFINITY.equals(depth)) - { - // In this case node is locked indirectly. - - //Get lock scope - String scope = (String) nodeService.getProperty(parent, WebDAVModel.PROP_LOCK_SCOPE); - - // Node has it's own Lock token. - - lockInfo.setDepth(depth); - lockInfo.setScope(scope); - lockInfo.setSharedLockTokens(LockInfo.parseSharedLockTokens(sharedLocks)); - - lockInfo.setShared(true); - - return lockInfo; - } - } - } } finally { + if (lockInfo == null) + { + lockInfo = new LockInfo(); + } // temporarily cache - for this request m_parentLockInfo.put(parent, lockInfo); } @@ -1221,32 +1168,11 @@ public abstract class WebDAVMethod private LockInfo getNodeLockInfoDirect(FileInfo nodeInfo) { - String propOpaqueLockToken = (String) nodeInfo.getProperties().get(WebDAVModel.PROP_OPAQUE_LOCK_TOKEN); - if (propOpaqueLockToken != null) + LockInfo lock = getLockStore().get(nodeInfo.getNodeRef()); + + if (lock != null && lock.isLocked()) { - // now check for lock status ... - LockStatus lockSts = getLockService().getLockStatus(nodeInfo.getNodeRef()); - if (lockSts == LockStatus.LOCKED || lockSts == LockStatus.LOCK_OWNER) - { - // Get lock depth - String depth = (String) nodeInfo.getProperties().get(WebDAVModel.PROP_LOCK_DEPTH); - //Get lock scope - String scope = (String) nodeInfo.getProperties().get(WebDAVModel.PROP_LOCK_SCOPE); - // Get shared lock tokens - String sharedLocks = (String) nodeInfo.getProperties().get(WebDAVModel.PROP_SHARED_LOCK_TOKENS); - - // Node has it's own Lock token. - // Store lock information to the lockInfo object - - LockInfo lockInfo = new LockInfo(); - - lockInfo.setToken(propOpaqueLockToken); - lockInfo.setDepth(depth); - lockInfo.setScope(scope); - lockInfo.setSharedLockTokens(LockInfo.parseSharedLockTokens(sharedLocks)); - - return lockInfo; - } + return lock; } return null; @@ -1254,37 +1180,15 @@ public abstract class WebDAVMethod private LockInfo getNodeLockInfoIndirect(FileInfo nodeInfo, NodeRef parent) { - NodeService nodeService = getNodeService(); + LockInfo parentLock = getLockStore().get(parent); - // Check node lock depth. - // If depth is WebDAV.INFINITY then return this node's Lock token. - String depth = (String) nodeService.getProperty(parent, WebDAVModel.PROP_LOCK_DEPTH); - if (WebDAV.INFINITY.equals(depth)) + if (parentLock != null && WebDAV.INFINITY.equals(parentLock.getDepth())) { // now check for lock status ... - LockStatus lockSts = getLockService().getLockStatus(parent); - if (lockSts == LockStatus.LOCKED || lockSts == LockStatus.LOCK_OWNER) + if (parentLock.isLocked()) { // In this case node is locked indirectly. - - //Get lock scope - String scope = (String) nodeInfo.getProperties().get(WebDAVModel.PROP_LOCK_SCOPE); - // Get shared lock tokens - String sharedLocks = (String) nodeInfo.getProperties().get(WebDAVModel.PROP_SHARED_LOCK_TOKENS); - - // Store lock information to the lockInfo object - - // Get lock token of the locked node - this is indirect lock token. - String propOpaqueLockToken = (String) nodeService.getProperty(parent, WebDAVModel.PROP_OPAQUE_LOCK_TOKEN); - - LockInfo lockInfo = new LockInfo(); - - lockInfo.setToken(propOpaqueLockToken); - lockInfo.setDepth(depth); - lockInfo.setScope(scope); - lockInfo.setSharedLockTokens(LockInfo.parseSharedLockTokens(sharedLocks)); - - return lockInfo; + return parentLock; } } diff --git a/source/java/org/alfresco/repo/webdav/WebDAVServlet.java b/source/java/org/alfresco/repo/webdav/WebDAVServlet.java index 9a98af6ec2..d96678f10d 100644 --- a/source/java/org/alfresco/repo/webdav/WebDAVServlet.java +++ b/source/java/org/alfresco/repo/webdav/WebDAVServlet.java @@ -280,9 +280,11 @@ public class WebDAVServlet extends HttpServlet NodeService nodeService = (NodeService) context.getBean("NodeService"); SearchService searchService = (SearchService) context.getBean("SearchService"); NamespaceService namespaceService = (NamespaceService) context.getBean("NamespaceService"); - + LockStoreFactory lockStoreFactory = (LockStoreFactory) context.getBean("webdavLockStoreFactory"); + LockStore lockStore = lockStoreFactory.getLockStore(); + // Create the WebDAV helper - m_davHelper = new WebDAVHelper(m_serviceRegistry, authService); + m_davHelper = new WebDAVHelper(m_serviceRegistry, lockStore, authService); // Initialize the root node