/* * Copyright (C) 2003-2010 eXo Platform SAS. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see<http://www.gnu.org/licenses/>. */ package org.exoplatform.services.jcr.impl.core.lock.cacheable; import org.exoplatform.services.jcr.core.ExtendedSession; import org.exoplatform.services.jcr.datamodel.NodeData; import org.exoplatform.services.jcr.impl.core.NodeImpl; import org.exoplatform.services.jcr.impl.core.SessionDataManager; import org.exoplatform.services.jcr.impl.core.SessionImpl; import org.exoplatform.services.jcr.impl.core.lock.AbstractSessionLockManager; import org.exoplatform.services.jcr.impl.core.lock.LockImpl; import org.exoplatform.services.jcr.util.IdGenerator; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; /** * Created by The eXo Platform SAS. * * <br>Date: * * @author <a href="karpenko.sergiy@gmail.com">Karpenko Sergiy</a> * @version $Id: CacheableSessionLockManager.java 2137 2010-03-25 15:31:56Z sergiykarpenko $ */ public class CacheableSessionLockManager extends AbstractSessionLockManager { /** * Logger */ private static final Log LOG = ExoLogger.getLogger("exo.jcr.component.core.CacheableSessionLockManager"); /** * Session identifier. */ private final String sessionID; /** * Lock tokens held by this session. * [token name, tokens hash] */ private final Map<String, String> tokens; /** * Map of nodes locked in this session. Need to remove session scoped lock on session close. * [node identifier, lock data] */ private final Map<String, LockData> lockedNodes; /** * Set of pending locked nodes identifiers. */ private final Set<String> pendingLocks; /** * Workspace lock manager */ private final CacheableLockManager lockManager; /** * Constructor. * * @param sessionID - session identifier * @param lockManager - workspace lock manager */ public CacheableSessionLockManager(String sessionID, CacheableLockManager lockManager, SessionDataManager transientManager) { super(transientManager); this.sessionID = sessionID; this.tokens = new HashMap<String, String>(); this.lockedNodes = new HashMap<String, LockData>(); this.pendingLocks = new HashSet<String>(); this.lockManager = lockManager; } /** * {@inheritDoc} */ public Lock addLock(NodeImpl node, boolean isDeep, boolean isSessionScoped, long timeOut) throws LockException, RepositoryException { String lockToken = IdGenerator.generate(); NodeData data = (NodeData)node.getData(); LockData lData = lockManager.getExactNodeOrCloseParentLock(data); if (lData != null) { if (lData.getNodeIdentifier().equals(node.getIdentifier())) { throw new LockException("Node already locked: " + data.getQPath()); } else if (lData.isDeep()) { throw new LockException("Parent node has deep lock."); } } if (isDeep && lockManager.getClosedChild(data) != null) { throw new LockException("Some child node is locked."); } String lockTokenHash = lockManager.getLockTokenHash(lockToken); lData = new LockData(node.getIdentifier(), lockTokenHash, isDeep, isSessionScoped, node.getSession().getUserID(), timeOut > 0 ? timeOut : lockManager.getDefaultLockTimeOut()); lockedNodes.put(node.getInternalIdentifier(), lData); pendingLocks.add(node.getInternalIdentifier()); tokens.put(lockToken, lData.getTokenHash()); LockImpl lock = new CacheLockImpl(node.getSession(), lData, this); return lock; } /** * {@inheritDoc} */ public void addLockToken(String lt) { tokens.put(lt, lockManager.getLockTokenHash(lt)); } /** * {@inheritDoc} */ public LockImpl getLock(NodeImpl node) throws LockException, RepositoryException { LockData lData = lockManager.getExactNodeOrCloseParentLock((NodeData)node.getData()); if (lData == null || (!node.getInternalIdentifier().equals(lData.getNodeIdentifier()) && !lData.isDeep())) { throw new LockException("Node not locked: " + node.getData().getQPath()); } return new CacheLockImpl(node.getSession(), lData, this); } /** * {@inheritDoc} */ public String[] getLockTokens() { String[] arr = new String[tokens.size()]; tokens.keySet().toArray(arr); return arr; } /** * {@inheritDoc} */ public boolean holdsLock(NodeData node) throws RepositoryException { return lockManager.lockExist(node.getIdentifier());//.getExactNodeLock(node) != null; } /** * {@inheritDoc} */ protected boolean isLockedPersisted(NodeData node) throws LockException { LockData lData = null; try { lData = lockManager.getExactNodeOrCloseParentLock(node); } catch (RepositoryException e) { throw new LockException(e.getMessage(), e); } if (lData == null || (!node.getIdentifier().equals(lData.getNodeIdentifier()) && !lData.isDeep())) { return false; } return true; } /** * {@inheritDoc} */ protected boolean isPersistedLockHolder(NodeData node) throws RepositoryException { LockData lData = lockManager.getExactNodeOrCloseParentLock(node); return lData != null && isLockHolder(lData); } /** * {@inheritDoc} */ public void onCloseSession(ExtendedSession session) { SessionImpl sessionImpl = (SessionImpl)session; int length = lockedNodes.size(); if (length > 0) { String[] nodeIds = new String[length]; lockedNodes.keySet().toArray(nodeIds); for (int i = 0; i < length; i++) { String nodeId = nodeIds[i]; LockData lock = lockedNodes.remove(nodeId); if (lock.isSessionScoped() && !pendingLocks.contains(nodeId)) { try { NodeImpl node = ((NodeImpl)sessionImpl.getTransientNodesManager() .getItemByIdentifier(lock.getNodeIdentifier(), false)); if (node != null) { node.unlock(); } } catch (UnsupportedRepositoryOperationException e) { LOG.error(e.getLocalizedMessage()); } catch (LockException e) { LOG.error(e.getLocalizedMessage()); } catch (AccessDeniedException e) { LOG.error(e.getLocalizedMessage()); } catch (RepositoryException e) { LOG.error(e.getLocalizedMessage()); } } } lockedNodes.clear(); } pendingLocks.clear(); tokens.clear(); lockManager.closeSessionLockManager(sessionID); } /** * {@inheritDoc} */ public void removeLockToken(String lt) { tokens.remove(lt); } /** * Checks if session has token to this lock data or session is System. * * @param lockData * @return */ private boolean isLockHolder(LockData lockData) { return tokens.containsValue(lockData.getTokenHash()); } /** * Is session contains pending lock for node by nodeId. * @param nodeId - node ID string * @return boolean */ public boolean containsPendingLock(String nodeId) { return pendingLocks.contains(nodeId); } /** * Returns real token, if session has it. * * @param tokenHash - token hash string * @return lock token string */ protected String getLockToken(String tokenHash) { for (String token : tokens.keySet()) { if (tokens.get(token).equals(tokenHash)) { return token; } } return null; } /** * Return pending lock. * * @param nodeId - ID of locked node * @return pending lock or null */ public LockData getPendingLock(String nodeId) { if (pendingLocks.contains(nodeId)) { return lockedNodes.get(nodeId); } else { return null; } } /** * Check is lock alive. That means lock must exist in LockManager storage (cache or map, etc). * * @param nodeIdentifier - locked node id * @return */ protected boolean isLockLive(String nodeIdentifier) throws LockException { if (lockManager.isLockLive(nodeIdentifier)) { return true; } else { return pendingLocks.contains(nodeIdentifier); } } public void notifyLockPersisted(String nodeIdentifier) { pendingLocks.remove(nodeIdentifier); } /** * Notify SessionLockManager that node is unlocked. * * @param nodeIdentifier - unlocked node identifier */ public void notifyLockRemoved(String nodeIdentifier) { lockedNodes.remove(nodeIdentifier); } /** * Refresh lockData. * * @param newLockData * @throws LockException */ protected void refresh(LockData newLockData) throws LockException { lockManager.refreshLockData(newLockData); } /** * {@inheritDoc} */ protected boolean checkPersistedLocks(NodeData node) throws LockException { LockData lData = null; try { lData = lockManager.getExactNodeOrCloseParentLock(node); } catch (RepositoryException e) { throw new LockException(e.getMessage(), e); } if (lData == null || (!node.getIdentifier().equals(lData.getNodeIdentifier()) && !lData.isDeep())) { return true; } // lock exist, so lets check is current session is LockHolder return isLockHolder(lData); } }