Files
alfresco-community-repo/source/java/org/alfresco/repo/webdav/WebDAVLockServiceImpl.java
Alan Davis 3e1e1edbaa Merged 5.2.N (5.2.1) to HEAD (5.2)
125788 rmunteanu: Merged 5.1.N (5.1.2) to 5.2.N (5.2.1)
      125606 rmunteanu: Merged 5.1.1 (5.1.1) to 5.1.N (5.1.2)
         125515 slanglois: MNT-16155 Update source headers - add new Copyrights for Java and JSP source files + automatic check in the build


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@127810 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2016-06-03 17:08:06 +00:00

487 lines
18 KiB
Java

/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
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.lock.LockUtils;
import org.alfresco.repo.lock.mem.Lifetime;
import org.alfresco.repo.lock.mem.LockState;
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.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;
/**
* <p>
* 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 WebDAVLockServiceImpl implements 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(WebDAVLockServiceImpl.class);
private static ThreadLocal<HttpSession> currentSession = new ThreadLocal<HttpSession>();
private LockService lockService;
private NodeService nodeService;
private TransactionService transactionService;
private CheckOutCheckInService checkOutCheckInService;
/**
* Set the LockService
*
* @param lockService LockService
*/
public void setLockService(LockService lockService)
{
this.lockService = lockService;
}
/**
* Set the NodeService
*
* @param nodeService NodeService
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* Set the TransactionService
*
* @param transactionService TransactionService
*/
public void setTransactionService(TransactionService transactionService)
{
this.transactionService = transactionService;
}
/**
* Set the CheckOutCheckInService
*
* @param checkOutCheckInService CheckOutCheckInService
*/
public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService)
{
this.checkOutCheckInService = checkOutCheckInService;
}
/**
* Caches current session to the thread local variable
*
* @param session HttpSession
*/
@Override
public void setCurrentSession(HttpSession session)
{
currentSession.set(session);
}
@Override
@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<Pair<String, NodeRef>> lockedResources = (List<Pair<String, NodeRef>>) 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<String, NodeRef> lockedResource : lockedResources)
{
String runAsUser = lockedResource.getFirst();
final NodeRef nodeRef = lockedResource.getSecond();
// there are some document that should be forcibly unlocked
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
@Override
public Void doWork() throws Exception
{
return transactionService.getRetryingTransactionHelper().doInTransaction(
new RetryingTransactionCallback<Void>()
{
@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());
}
}
}
public void lock(NodeRef nodeRef, LockInfo lockInfo)
{
boolean performSessionBehavior = false;
long timeout;
timeout = lockInfo.getRemainingTimeoutSeconds();
// 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;
lockInfo.setTimeoutSeconds((int) timeout);
performSessionBehavior = true;
}
// TODO: lock children according to depth? lock type?
final String additionalInfo = lockInfo.toJSON();
lockService.lock(nodeRef, LockType.WRITE_LOCK, (int) timeout, Lifetime.EPHEMERAL, additionalInfo);
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<String, NodeRef>(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 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 userName userName
* @param timeout the number of seconds before the locks expires
*/
@Override
public void lock(NodeRef nodeRef, String userName, int timeout)
{
LockInfo lockInfo = createLock(nodeRef, userName, true, timeout);
lock(nodeRef, lockInfo);
}
/**
* 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
*/
@Override
public void unlock(NodeRef nodeRef)
{
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<String, NodeRef>(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(org.alfresco.service.cmr.repository.NodeRef, String)
*
* @param nodeRef the node reference
* @return the lock status
*/
@Override
public LockInfo getLockInfo(NodeRef nodeRef)
{
LockInfo lockInfo = null;
LockState lockState = lockService.getLockState(nodeRef);
if (lockState != null)
{
String additionalInfo = lockState.getAdditionalInfo();
try
{
lockInfo = LockInfoImpl.fromJSON(additionalInfo);
}
catch (IllegalArgumentException e)
{
lockInfo = new LockInfoImpl();
}
lockInfo.setExpires(lockState.getExpires());
lockInfo.setOwner(lockState.getOwner());
}
return lockInfo;
}
/**
* Determines if the node is locked AND it's not a WRITE_LOCK for the current user.<p>
*
* @return true if the node is locked AND it's not a WRITE_LOCK for the current user
*/
public boolean isLockedAndReadOnly(NodeRef nodeRef)
{
return LockUtils.isLockedAndReadOnly(nodeRef, this.lockService);
}
/**
* Add the given <code>object</code> to the session list that is stored in session under <code>listName</code> 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<Object> list = null;
synchronized (session)
{
list = (List<Object>) session.getAttribute(listName);
if (list == null)
{
list = new ArrayList<Object>();
session.setAttribute(listName, list);
}
}
synchronized (list)
{
if (!list.contains(object))
{
list.add(object);
}
}
}
/**
* Removes the given <code>object</code> from the session list that is stored in session under <code>listName</code> attribute
*
* @param session the session
* @param listName the list name (session attribute name)
* @param object the object to store in session list
*
* @return <tt>true</tt> if session list contained the specified element, otherwise <tt>false</tt>
*/
@SuppressWarnings("unchecked")
private static final boolean removeObjectFromSessionList(HttpSession session, String listName, Object object)
{
List<Object> list = null;
synchronized (session)
{
list = (List<Object>) session.getAttribute(listName);
}
if (list == null)
{
return false;
}
synchronized (list)
{
return list.remove(object);
}
}
/**
* Create a new lock
*
* @param nodeRef NodeRef
* @param userName String
* @param createExclusive boolean
* @param timeoutSecs int
*/
private LockInfo createLock(NodeRef nodeRef, String userName, boolean createExclusive, int timeoutSecs)
{
// Create Lock token
String lockToken = WebDAV.makeLockToken(nodeRef, userName);
LockInfo lockInfo = new LockInfoImpl();
if (createExclusive)
{
// Lock the node
lockInfo.setTimeoutSeconds(timeoutSecs);
lockInfo.setExclusiveLockToken(lockToken);
}
else
{
lockInfo.addSharedLockToken(lockToken);
}
// Store lock depth
lockInfo.setDepth(WebDAV.getDepthName(WebDAV.DEPTH_INFINITY));
// Store lock scope (shared/exclusive)
String scope = createExclusive ? WebDAV.XML_EXCLUSIVE : WebDAV.XML_SHARED;
lockInfo.setScope(scope);
// Store the owner of this lock
lockInfo.setOwner(userName);
// TODO: to help with debugging/refactoring (remove later)
String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
if (!currentUser.equals(userName))
{
throw new IllegalStateException("Node is being locked for user " + userName +
" by (different/current) user " + currentUser);
}
return lockInfo;
}
}