/*
 * 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.lock;
import static org.junit.Assert.assertNotEquals;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.lock.mem.Lifetime;
import org.alfresco.repo.lock.mem.LockState;
import org.alfresco.repo.lock.mem.LockStore;
import org.alfresco.repo.search.IndexerAndSearcher;
import org.alfresco.repo.search.SearcherComponent;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.permissions.AccessDeniedException;
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.lock.NodeLockedException;
import org.alfresco.service.cmr.lock.UnableToAquireLockException;
import org.alfresco.service.cmr.lock.UnableToReleaseLockException;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.QName;
import org.alfresco.test_category.BaseSpringTestsCategory;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.BaseSpringTest;
import org.alfresco.util.TestWithUserUtils;
import org.junit.Test;
import org.junit.experimental.categories.Category;
/**
 * Simple lock service test
 * 
 * @author Roy Wetherall
 */
@Category(BaseSpringTestsCategory.class)
public class LockServiceImplTest extends BaseSpringTest
{
    /**
     * Services used in tests
     */
    private NodeService nodeService;
    private LockService lockService;
    private MutableAuthenticationService authenticationService;
    private CheckOutCheckInService cociService;
    
    private PermissionService permissionService;
    private LockService securedLockService;
    /**
     * Data used in tests
     */
    private NodeRef parentNode;
    private NodeRef childNode1;
    private NodeRef childNode2;    
    private NodeRef noAspectNode;
    private NodeRef checkedOutNode;
    
    private static final String GOOD_USER_NAME = "goodUser";
    private static final String BAD_USER_NAME = "badUser";
    private static final String PWD = "password";
    
    NodeRef rootNodeRef;
    private StoreRef storeRef;
    /**
     * Called during the transaction setup
     */
    protected void onSetUpInTransaction() throws Exception
    {
        this.nodeService = (NodeService)applicationContext.getBean("dbNodeService");
        this.lockService = (LockService)applicationContext.getBean("lockService");
        
        this.securedLockService = (LockService)applicationContext.getBean("LockService");
        this.permissionService = (PermissionService)applicationContext.getBean("PermissionService");
        
        this.authenticationService = (MutableAuthenticationService)applicationContext.getBean("authenticationService");
        this.cociService = (CheckOutCheckInService) applicationContext.getBean("checkOutCheckInService");
        
        // Set the authentication
        AuthenticationComponent authComponent = (AuthenticationComponent)this.applicationContext.getBean("authenticationComponent");
        authComponent.setSystemUserAsCurrentUser();        
        
        // Create the node properties
        HashMap nodeProperties = new HashMap();
        nodeProperties.put(QName.createQName("{test}property1"), "value1");
        
        // Create a workspace that contains the 'live' nodes
        storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis());
        
        // Get a reference to the root node
        rootNodeRef = this.nodeService.getRootNode(storeRef);
        
        // Create node 
        this.parentNode = this.nodeService.createNode(
                rootNodeRef, 
				ContentModel.ASSOC_CHILDREN, 
                QName.createQName("{}ParentNode"),
                ContentModel.TYPE_CONTAINER,
                nodeProperties).getChildRef();
        this.nodeService.addAspect(this.parentNode, ContentModel.ASPECT_LOCKABLE, new HashMap());
        HashMap audProps = new HashMap();
        audProps.put(ContentModel.PROP_CREATOR, "Monkey");
        this.nodeService.addAspect(this.parentNode, ContentModel.ASPECT_AUDITABLE, audProps);
        assertNotNull(this.parentNode);
        
        // Add some children to the node
        this.childNode1 = this.nodeService.createNode(
                this.parentNode,
				ContentModel.ASSOC_CHILDREN,
                QName.createQName("{}ChildNode1"),
                ContentModel.TYPE_CONTAINER,
                nodeProperties).getChildRef();
        this.nodeService.addAspect(this.childNode1, ContentModel.ASPECT_LOCKABLE, new HashMap());
        assertNotNull(this.childNode1);
        this.childNode2 = this.nodeService.createNode(
                this.parentNode,
				ContentModel.ASSOC_CHILDREN,
                QName.createQName("{}ChildNode2"),
                ContentModel.TYPE_CONTAINER,
                nodeProperties).getChildRef();
        this.nodeService.addAspect(this.childNode2, ContentModel.ASPECT_LOCKABLE, new HashMap());
        assertNotNull(this.childNode2);
        
        // Create a node with no lockAspect
        this.noAspectNode = this.nodeService.createNode(
                rootNodeRef, 
				ContentModel.ASSOC_CHILDREN, 
                QName.createQName("{}noAspectNode"),
                ContentModel.TYPE_CONTAINER,
                nodeProperties).getChildRef();
        assertNotNull(this.noAspectNode);
        
        // Create node with checkedOut
        this.checkedOutNode = this.nodeService.createNode(
                rootNodeRef, 
                ContentModel.ASSOC_CHILDREN, 
                QName.createQName("{}checkedOutNode"),
                ContentModel.TYPE_CONTAINER,
                nodeProperties).getChildRef();
        assertNotNull(this.checkedOutNode);
        
        // Check out test file
        NodeRef fileWorkingCopyNodeRef = cociService.checkout(checkedOutNode);
        assertNotNull(fileWorkingCopyNodeRef);
        assertTrue(nodeService.hasAspect(checkedOutNode, ContentModel.ASPECT_CHECKED_OUT));
        assertTrue(nodeService.hasAspect(checkedOutNode, ContentModel.ASPECT_LOCKABLE));
        
        
        // Create the  users
        TestWithUserUtils.createUser(GOOD_USER_NAME, PWD, rootNodeRef, this.nodeService, this.authenticationService);
        TestWithUserUtils.createUser(BAD_USER_NAME, PWD, rootNodeRef, this.nodeService, this.authenticationService);
        
        this.permissionService.setPermission(rootNodeRef, GOOD_USER_NAME, PermissionService.ALL_PERMISSIONS, true);
        this.permissionService.setPermission(rootNodeRef, BAD_USER_NAME, PermissionService.CHECK_OUT, true);
        this.permissionService.setPermission(rootNodeRef, BAD_USER_NAME, PermissionService.WRITE, true);
        this.permissionService.setPermission(rootNodeRef, BAD_USER_NAME, PermissionService.READ, true);
        
        // Stash the user node ref's for later use
        TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
    }
    
    /**
     * Test lock
     */
    public void testLock()
    {
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        // Check that the node is not currently locked
        assertEquals(
                LockStatus.NO_LOCK, 
                this.lockService.getLockStatus(this.parentNode));
 
        
        // Test valid lock
        this.lockService.lock(this.parentNode, LockType.WRITE_LOCK);
        assertEquals(
                LockStatus.LOCK_OWNER, 
                this.lockService.getLockStatus(this.parentNode));
        
        // Check that we can retrieve LockState
        LockState lockState = lockService.getLockState(parentNode);
        assertEquals(parentNode, lockState.getNodeRef());
        assertEquals(LockType.WRITE_LOCK, lockState.getLockType());
        assertEquals(GOOD_USER_NAME, lockState.getOwner());
        assertEquals(Lifetime.PERSISTENT, lockState.getLifetime());
        assertEquals(null, lockState.getExpires());
        assertEquals(null, lockState.getAdditionalInfo());
        
        // Check the correct properties have been set
        Map props = nodeService.getProperties(parentNode);
        assertEquals(GOOD_USER_NAME, props.get(ContentModel.PROP_LOCK_OWNER));
        assertEquals(LockType.WRITE_LOCK.toString(), props.get(ContentModel.PROP_LOCK_TYPE));
        assertEquals(Lifetime.PERSISTENT.toString(), props.get(ContentModel.PROP_LOCK_LIFETIME));
        assertEquals(null, props.get(ContentModel.PROP_EXPIRY_DATE));
        
        TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        assertEquals(
                LockStatus.LOCKED,
                this.lockService.getLockStatus(this.parentNode));
     
        // Test lock when already locked
        try
        {
            this.lockService.lock(this.parentNode, LockType.WRITE_LOCK);
            fail("The user should not be able to lock the node since it is already locked by another user.");
        }
        catch (UnableToAquireLockException exception)
        {
            System.out.println(exception.getMessage());
        }
        
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        // Test already locked by this user
        try
        {
            this.lockService.lock(this.parentNode, LockType.WRITE_LOCK);
        }
        catch (Exception exception)
        {
            fail("No error should be thrown when a node is re-locked by the current lock owner.");
        }
        
        // Test with no apect node
        this.lockService.lock(this.noAspectNode, LockType.WRITE_LOCK);        
    }
    
    public void testPersistentLockMayStoreAdditionalInfo()
    {
        lockService.lock(noAspectNode, LockType.NODE_LOCK, 0, Lifetime.PERSISTENT, "additional info");
        LockState lockState = lockService.getLockState(noAspectNode);
        assertEquals("additional info", lockState.getAdditionalInfo());
    }
    
    public void testEphemeralLock()
    {
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        // Check that the node is not currently locked
        assertEquals(LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode));
        
        // Check that there really is no lockable aspect
        assertEquals(false, nodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE));
        
        // Lock the node 
        lockService.lock(noAspectNode, LockType.WRITE_LOCK, 86400, Lifetime.EPHEMERAL, "some extra data");
        
        // Check additionalInfo has been stored
        assertEquals("some extra data", lockService.getAdditionalInfo(noAspectNode));
        
        // Check that we can retrieve LockState
        LockState lockState = lockService.getLockState(noAspectNode);
        assertEquals(noAspectNode, lockState.getNodeRef());
        assertEquals(LockType.WRITE_LOCK, lockState.getLockType());
        assertEquals(GOOD_USER_NAME, lockState.getOwner());
        assertEquals(Lifetime.EPHEMERAL, lockState.getLifetime());
        assertNotNull(lockState.getExpires());
        assertEquals("some extra data", lockState.getAdditionalInfo());
        
        // The node should be locked
        assertEquals(LockStatus.LOCK_OWNER, lockService.getLockStatus(noAspectNode));
        // The node must still not have the lockable aspect applied
        assertEquals(false, nodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE));
        // ...though the full node service should report that it is present
        NodeService fullNodeService = (NodeService) applicationContext.getBean("nodeService");
        assertEquals(true, fullNodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE));
        
        TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        assertEquals(LockStatus.LOCKED, lockService.getLockStatus(noAspectNode));
        
        // Test lock when already locked
        try
        {
            lockService.lock(noAspectNode, LockType.WRITE_LOCK);
            fail("The user should not be able to lock the node since it is already locked by another user.");
        }
        catch (UnableToAquireLockException exception)
        {
            System.out.println(exception.getMessage());
        }
        
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        assertEquals(LockStatus.LOCK_OWNER, lockService.getLockStatus(noAspectNode));
        
        // Test already locked by this user - relock
        try
        {
            lockService.lock(noAspectNode, LockType.WRITE_LOCK, 0, Lifetime.EPHEMERAL);
        }
        catch (Exception exception)
        {
            fail("No error should be thrown when a node is re-locked by the current lock owner.");
        } 
        
        // The node should be locked
        assertEquals(LockStatus.LOCK_OWNER, lockService.getLockStatus(noAspectNode));
        // If we remove the lock info directly from the memory store then the node should no longer
        // be reported as locked (as it is an ephemeral lock)
        LockStore lockStore = (LockStore) applicationContext.getBean("lockStore");
        lockStore.clear();
        // The node must no longer be reported as locked
        assertEquals(LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode));
        
        // Lock again, ready to test unlocking an ephemeral lock.
        try
        {
            lockService.lock(noAspectNode, LockType.WRITE_LOCK, 0, Lifetime.EPHEMERAL);
        }
        catch (Exception exception)
        {
            fail("No error should be thrown when a node is re-locked by the current lock owner.");
        }
        assertEquals(LockStatus.LOCK_OWNER, lockService.getLockStatus(noAspectNode));
        lockService.unlock(noAspectNode);        
        assertEquals(LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode));
    }
    @Test
    public void testEphemeralLockIndexing()
    {
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, authenticationService);
        
        IndexerAndSearcher indexerAndSearcher = (IndexerAndSearcher)
                    applicationContext.getBean("indexerAndSearcherFactory");
        SearcherComponent searcher = new SearcherComponent();
        searcher.setIndexerAndSearcherFactory(indexerAndSearcher);
        // Create a lock (owned by the current user)
        lockService.lock(noAspectNode, LockType.WRITE_LOCK, 86400, Lifetime.EPHEMERAL);
        
        // Query for the user's locks
        final String query = String.format("+@cm\\:lockOwner:\"%s\" +@cm\\:lockType:\"WRITE_LOCK\"", GOOD_USER_NAME);
        ResultSet rs = searcher.query(storeRef, "lucene", query);
        assertTrue(rs.getNodeRefs().contains(noAspectNode));
        
        // Unlock the node
        lockService.unlock(noAspectNode);
        
        // Perform a new search, the index should reflect that it is not locked.
        rs = searcher.query(storeRef, "lucene", query);
        assertFalse(rs.getNodeRefs().contains(noAspectNode));
    }
    
    /* MNT-10477 related test */
    @Test
    public void testEphemeralLockModifyNode()
    {
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        // Check that the node is not currently locked
        assertEquals(LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode));
        
        // Check that there really is no lockable aspect
        assertEquals(false, nodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE));
        
        // Lock the node 
        lockService.lock(noAspectNode, LockType.WRITE_LOCK, 86400, Lifetime.EPHEMERAL, "some extra data");
        
        // get bad user
        TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        assertEquals(LockStatus.LOCKED, lockService.getLockStatus(noAspectNode));
        
        NodeService fullNodeService = (NodeService) applicationContext.getBean("nodeService");
        
        /* addProperties test */
        try
        {
            Map props = new HashMap();
            props.put(ContentModel.PROP_DESCRIPTION, "descr" + System.currentTimeMillis());
            props.put(ContentModel.PROP_TITLE, "title" + System.currentTimeMillis());
            fullNodeService.addProperties(noAspectNode, props);
            
            fail();
        }
        catch(NodeLockedException e)
        {
            // it's ok - node supposed to be locked
        }
        
        /* setProperty test */
        try
        {
            fullNodeService.setProperty(noAspectNode, ContentModel.PROP_DESCRIPTION, "descr" + System.currentTimeMillis());
            
            fail();
        }
        catch(NodeLockedException e)
        {
            // it's ok - node supposed to be locked
        }
        
        /* setProperties test */
        try
        {
            Map props = new HashMap();
            props.put(ContentModel.PROP_DESCRIPTION, "descr" + System.currentTimeMillis());
            props.put(ContentModel.PROP_TITLE, "title" + System.currentTimeMillis());
            fullNodeService.setProperties(noAspectNode, props);
            
            fail();
        }
        catch(NodeLockedException e)
        {
            // it's ok - node supposed to be locked
        }
        
        /* removeProperty test */
        try
        {
            fullNodeService.removeProperty(noAspectNode, ContentModel.PROP_DESCRIPTION);
            
            fail();
        }
        catch(NodeLockedException e)
        {
            // it's ok - node supposed to be locked
        }
        
        /* addAspect test */
        try
        {
            fullNodeService.addAspect(noAspectNode, ContentModel.ASPECT_AUTHOR , null);
            
            fail();
        }
        catch(NodeLockedException e)
        {
            // it's ok - node supposed to be locked
        }
        
        /* removeAspect test */
        try
        {
            fullNodeService.removeAspect(noAspectNode, ContentModel.ASPECT_AUTHOR);
            
            fail();
        }
        catch(NodeLockedException e)
        {
            // it's ok - node supposed to be locked
        }
        
        /* setType test */
        try
        {
            fullNodeService.setType(noAspectNode, ContentModel.TYPE_CMOBJECT);
            
            fail();
        }
        catch(NodeLockedException e)
        {
            // it's ok - node supposed to be locked
        }
        
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        lockService.unlock(noAspectNode);        
        assertEquals(LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode));
    }
    
    public void testLockRevertedOnRollback() throws NotSupportedException, SystemException
    {
        // Preconditions of test
        assertEquals(LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode));
        assertEquals(LockStatus.NO_LOCK, lockService.getLockStatus(rootNodeRef));
        
        // Lock noAspectNode
        lockService.lock(noAspectNode, LockType.WRITE_LOCK, 0, Lifetime.EPHEMERAL);
        
        // Lock rootNodeRef
        lockService.lock(rootNodeRef, LockType.NODE_LOCK, 0, Lifetime.EPHEMERAL);
        
        // Sometime later, a refresh occurs (so this should not be reverted to unlocked, but to this state)
        lockService.lock(rootNodeRef, LockType.NODE_LOCK, 3600, Lifetime.EPHEMERAL);
        
        // Rollback
        endTransaction();
        
        // This lock should not be present.
        assertEquals(LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode));
        
        // This lock should still be present.
        assertEquals(LockStatus.LOCK_OWNER, lockService.getLockStatus(rootNodeRef));
    }
    
    /**
     * Test lock with lockChildren == true
     */
    // TODO
    public void testLockChildren()
    {
    }
    
    /**
     * Test lock with collection
     */
    // TODO
    public void testLockMany()
    {
    }
    
    /**
     * Test unlock node
     */
    public void testUnlock()
    {
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        // Lock the parent node
        testLock();
        
        TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        // Try and unlock a locked node
        try
        {
            this.lockService.unlock(this.parentNode);
            // This will pass in the open workd
            //fail("A user cannot unlock a node that is currently lock by another user.");
        }
        catch (UnableToReleaseLockException exception)
        {
            System.out.println(exception.getMessage());
        }
        
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        // Unlock the node
        this.lockService.unlock(this.parentNode);
        assertEquals(
                LockStatus.NO_LOCK,
                this.lockService.getLockStatus(this.parentNode));
        
        TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        assertEquals(
                LockStatus.NO_LOCK,
                this.lockService.getLockStatus(this.parentNode));
        
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        // Try and unlock node with no lock
        try
        {
            this.lockService.unlock(this.parentNode);
        }
        catch (Exception exception)
        {
            fail("Unlocking an unlocked node should not result in an exception being raised.");
        }
        
        // Test with no apect node
        this.lockService.unlock(this.noAspectNode);
    }
    
    // TODO
    public void testUnlockChildren()
    {
    }
    
    // TODO
    public void testUnlockMany()
    {
    }
    
    /**
     * Test getLockStatus
     */
    public void testGetLockStatus()
    {
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        // Check an unlocked node
        LockStatus lockStatus1 = this.lockService.getLockStatus(this.parentNode);
        assertEquals(LockStatus.NO_LOCK, lockStatus1);
        
        this.lockService.lock(this.parentNode, LockType.WRITE_LOCK);
        
        TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        // Check for locked status
        LockStatus lockStatus2 = this.lockService.getLockStatus(this.parentNode);
        assertEquals(LockStatus.LOCKED, lockStatus2);
        
        // Check lockstore is not used for persistent locks
        // Previously LockStore was doubling up as a cache - the change in requirements means a test
        // is necessary to ensure the work has been implemented correctly (despite being an odd test)
        LockStore lockStore = (LockStore) applicationContext.getBean("lockStore");
        lockStore.clear();
        LockState lockState = lockStore.get(parentNode);
        // Nothing stored against node ref
        assertNull(lockState);
        lockService.getLockStatus(parentNode);
        // In-memory store still empty - only used for ephemeral locks
        lockState = lockStore.get(parentNode);
        assertNull(lockState);
        
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        // Check for lock owner status
        LockStatus lockStatus3 = this.lockService.getLockStatus(this.parentNode);
        assertEquals(LockStatus.LOCK_OWNER, lockStatus3);
                
        // Test with no apect node
        this.lockService.getLockStatus(this.noAspectNode);
        
        // Test method overload
        LockStatus lockStatus4 = this.lockService.getLockStatus(this.parentNode); 
        assertEquals(LockStatus.LOCK_OWNER, lockStatus4);
    }
    
    public void testGetLocks()
    {
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        LockServiceImpl lockService = (LockServiceImpl) this.lockService;
        List locked1 = lockService.getLocks(this.storeRef);
        assertNotNull(locked1);
        assertEquals(0, locked1.size());
        
        this.lockService.lock(this.parentNode, LockType.WRITE_LOCK);
        this.lockService.lock(this.childNode1, LockType.WRITE_LOCK);
        this.lockService.lock(this.childNode2, LockType.READ_ONLY_LOCK);
        
        List locked2 = lockService.getLocks(this.storeRef);
        assertNotNull(locked2);
        assertEquals(3, locked2.size());
        
        List locked3 = lockService.getLocks(this.storeRef, LockType.WRITE_LOCK);
        assertNotNull(locked3);
        assertEquals(2, locked3.size());
        
        List locked4 = lockService.getLocks(this.storeRef, LockType.READ_ONLY_LOCK);
        assertNotNull(locked4);
        assertEquals(1, locked4.size());
        
        TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        List locked5 = lockService.getLocks(this.storeRef);
        assertNotNull(locked5);
        assertEquals(0, locked5.size());
    }
    
    /**
     * Test getLockType
     */
    public void testGetLockType()
    {
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        // Get the lock type (should be null since the object is not locked)
        LockType lockType1 = this.lockService.getLockType(this.parentNode);
        assertNull(lockType1);
        
        // Lock the object for writing
        this.lockService.lock(this.parentNode, LockType.WRITE_LOCK);
        LockType lockType2 = this.lockService.getLockType(this.parentNode);
        assertNotNull(lockType2);               
        assertEquals(LockType.WRITE_LOCK, lockType2);
        
        // Unlock the node
        this.lockService.unlock(this.parentNode);
        LockType lockType3 = this.lockService.getLockType(this.parentNode);
        assertNull(lockType3);
        
        // Lock the object for read only
        this.lockService.lock(this.parentNode, LockType.READ_ONLY_LOCK);
        LockType lockType4 = this.lockService.getLockType(this.parentNode);
        assertNotNull(lockType4);
        assertEquals(LockType.READ_ONLY_LOCK, lockType4);
        
        // Lock the object for node lock
        this.lockService.lock(this.parentNode, LockType.NODE_LOCK);
        LockType lockType5 = this.lockService.getLockType(this.parentNode);
        assertNotNull(lockType5);               
        assertEquals(LockType.NODE_LOCK, lockType5);
        
        // Unlock the node
        this.lockService.unlock(this.parentNode);
        LockType lockType6 = this.lockService.getLockType(this.parentNode);
        assertNull(lockType6);
       
        // Test with no apect node
        LockType lockType7 = this.lockService.getLockType(this.noAspectNode);
        assertTrue("lock type is not null", lockType7 == null);
    }
    
    public void testGetLockTypeEphemeral()
    {
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        // Get the lock type (should be null since the object is not locked)
        LockType lockType1 = this.lockService.getLockType(this.parentNode);
        assertNull(lockType1);
        
        // Lock the object for writing
        this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 0, Lifetime.EPHEMERAL);
        LockType lockType2 = this.lockService.getLockType(this.parentNode);
        assertNotNull(lockType2);               
        assertEquals(LockType.WRITE_LOCK, lockType2);
        
        // Unlock the node
        this.lockService.unlock(this.parentNode);
        LockType lockType3 = this.lockService.getLockType(this.parentNode);
        assertNull(lockType3);
        
        // Lock the object for read only
        this.lockService.lock(this.parentNode, LockType.READ_ONLY_LOCK, 0, Lifetime.EPHEMERAL);
        LockType lockType4 = this.lockService.getLockType(this.parentNode);
        assertNotNull(lockType4);
        assertEquals(LockType.READ_ONLY_LOCK, lockType4);
        
        // Lock the object for node lock
        this.lockService.lock(this.parentNode, LockType.NODE_LOCK, 0, Lifetime.EPHEMERAL);
        LockType lockType5 = this.lockService.getLockType(this.parentNode);
        assertNotNull(lockType5);               
        assertEquals(LockType.NODE_LOCK, lockType5);
        
        // Unlock the node
        this.lockService.unlock(this.parentNode);
        LockType lockType6 = this.lockService.getLockType(this.parentNode);
        assertNull(lockType6);
        
        // Test with no apect node
        LockType lockType7 = this.lockService.getLockType(this.noAspectNode);
        assertTrue("lock type is not null", lockType7 == null);
    }
    
    public void testTimeToExpire()
    {
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 1);
        assertEquals(LockStatus.LOCK_OWNER, this.lockService.getLockStatus(this.parentNode));
        
        TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
        assertEquals(LockStatus.LOCKED, this.lockService.getLockStatus(this.parentNode));
        
        // Wait for 2 second before re-testing the status
        try {Thread.sleep(2*1000);} catch (Exception exception){};
        
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        assertEquals(LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode));
        
        TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        assertEquals(LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode));
        
        // Re-lock and then update the time to expire before lock expires
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 0);
        try
        {
            TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
            this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 1);
            fail("Can not update lock info if not lock owner");
        }
        catch (UnableToAquireLockException exception)
        {
            // Expected
        }
        
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 1);
        assertEquals(LockStatus.LOCK_OWNER, this.lockService.getLockStatus(this.parentNode));
        
        TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        assertEquals(LockStatus.LOCKED, this.lockService.getLockStatus(this.parentNode));
        
        // Wait for 2 second before re-testing the status
        try {Thread.sleep(2*1000);} catch (Exception exception){};
        
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        assertEquals(LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode));
        
        TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        assertEquals(LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode));
    }
    
    public void testEphemeralExpiryThreshold()
    {
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        final int origThresh = ((LockServiceImpl)lockService).getEphemeralExpiryThreshold();
        // Check the default situation is that the threshold does not apply.
        assertEquals(LockServiceImpl.MAX_EPHEMERAL_LOCK_SECONDS, origThresh);
        try
        {
            // Set the ephemeral expiry threshold to a much smaller value than the default
            // so that it takes effect.
            lockService.setEphemeralExpiryThreshold(300);
            
            // Check for an expiry time that should be unaffected by the threshold.
            checkLifetimeForExpiry(Lifetime.EPHEMERAL, 0, Lifetime.EPHEMERAL);
            checkLifetimeForExpiry(Lifetime.EPHEMERAL, 150, Lifetime.EPHEMERAL);
            
            // Check the largest allowed ephemeral expiry time.
            checkLifetimeForExpiry(Lifetime.EPHEMERAL, 300, Lifetime.EPHEMERAL);
            
            // When the expiry is greater than the threshold, then the lock should be
            // applied as a persistent lock.
            checkLifetimeForExpiry(Lifetime.PERSISTENT, 301, Lifetime.EPHEMERAL);
            
            // Switch off ephemeral locks entirely
            lockService.setEphemeralExpiryThreshold(-1);
            // Always persistent...
            checkLifetimeForExpiry(Lifetime.PERSISTENT, 0, Lifetime.EPHEMERAL);
            checkLifetimeForExpiry(Lifetime.PERSISTENT, 150, Lifetime.EPHEMERAL);
            checkLifetimeForExpiry(Lifetime.PERSISTENT, 300, Lifetime.EPHEMERAL);
            checkLifetimeForExpiry(Lifetime.PERSISTENT, 301, Lifetime.EPHEMERAL);
        }
        finally
        {
            lockService.setEphemeralExpiryThreshold(origThresh);
        }
    }
    
    private void checkLifetimeForExpiry(Lifetime expectedLifetime, int expirySecs, Lifetime requestedLifetime)
    {
        lockService.unlock(parentNode);
        assertNotEquals(LockStatus.LOCKED ,lockService.getLockStatus(parentNode));
        lockService.lock(parentNode, LockType.WRITE_LOCK, expirySecs, requestedLifetime);
        LockState lock = lockService.getLockState(parentNode);
        assertEquals(expectedLifetime, lock.getLifetime());
        
        // Check that for any timeouts we test, a request for a persistent lock always yields a persistent lock.
        lockService.unlock(parentNode);
        assertNotEquals(LockStatus.LOCKED ,lockService.getLockStatus(parentNode));
        lockService.lock(parentNode, LockType.WRITE_LOCK, expirySecs, Lifetime.PERSISTENT);
        lock = lockService.getLockState(parentNode);
        assertEquals(Lifetime.PERSISTENT, lock.getLifetime());
    }
    
    /**
     * Unit test to validate the behaviour of creating children of locked nodes.
     * No lock - can create children
     * READ_ONLY_LOCK - can't create children
     * WRITE_LOCK - owner can create children
     *      non owner can't create children
     * NODE_LOCK non owner can create children
     *     owner can create children     
     */
    public void testCreateChildrenOfLockedNodes() throws Exception
    {
      
      /**
       * Check we can create a child of an unlocked node.  
       */
      assertEquals(
      LockStatus.NO_LOCK, 
      this.lockService.getLockStatus(this.parentNode));       
      
      ChildAssociationRef child = nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildA"), ContentModel.TYPE_FOLDER);
        
      TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
      
      this.lockService.lock(this.parentNode, LockType.WRITE_LOCK);
      
      // Owner can create children
      child = nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildB"), ContentModel.TYPE_FOLDER);
      
      TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
      
      try
      {
          // Non owner can't create children with a write lock in place
          child = nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildB"), ContentModel.TYPE_FOLDER);
          fail("could create a child with a read only lock");
      } 
      catch (NodeLockedException e)
      {
          logger.debug("exception while trying to create a child of a read only lock", e);
      }  
      
      TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
      
      this.lockService.lock(this.parentNode, LockType.NODE_LOCK);
      
      // owner can create children with a node lock
      child = nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildD"), ContentModel.TYPE_FOLDER);
 
      TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
      
      // Non owner can create children with a node lock
      child = nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildC"), ContentModel.TYPE_FOLDER);
      
      TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
           
      this.lockService.lock(this.parentNode, LockType.READ_ONLY_LOCK);
      
      // owner should not be able to create children with a READ_ONLY_LOCK
      try
      {
          child = nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildD"), ContentModel.TYPE_FOLDER);
          fail("could create a child with a read only lock");
      } 
      catch (NodeLockedException e)
      {
          logger.debug("exception while trying to create a child of a read only lock", e);
      }
      
      TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
      
      // Non owner should not be able to create children with READ_ONLY_LOCK
      try
      {
          child = nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildE"), ContentModel.TYPE_FOLDER);
          fail("could create a child with a read only lock");
      } 
      catch (NodeLockedException e)
      {
          logger.debug("exception while trying to create a child of a read only lock", e);
      }      
    }
    
    /**
     * Test that it is impossible to unlock a checked out node
     */
    public void testUnlockCheckedOut() throws Exception
    {
        TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        try
        {
            this.lockService.unlock(checkedOutNode);
            fail("could unlock a checked out node");
        }
        catch (UnableToReleaseLockException e)
        {
            logger.debug("exception while trying to unlock a checked out node", e);
        }
    }
    
    @SuppressWarnings("deprecation")
    public void testUnlockNodeWithAdminUserAndAllPermissionsUser()
    {
        for (Lifetime lt : new Lifetime[]{Lifetime.EPHEMERAL, Lifetime.PERSISTENT})
        {
            TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
            /* create node */
            final NodeRef testNode = 
                this.nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{}testNode"), ContentModel.TYPE_CONTAINER).getChildRef();
        
            // lock it as GOOD user
            this.securedLockService.lock(testNode, LockType.WRITE_LOCK, 2 * 86400, lt, null);
        
            TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
        
            try
            {
                // try to unlock as bad user
                this.securedLockService.unlock(testNode);
                fail("BAD user shouldn't be able to unlock " + lt + " lock");
            }
            catch(AccessDeniedException e)
            {
                // expected expetion
            }
        
            TestWithUserUtils.authenticateUser(AuthenticationUtil.getAdminUserName(), "admin", rootNodeRef, this.authenticationService);
        
            // try to unlock as ADMIN user
            this.securedLockService.unlock(testNode);
            
            // test that bad use able to lock/unlock node
            TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
            this.securedLockService.lock(testNode, LockType.WRITE_LOCK, 2 * 86400, lt, null);
            this.securedLockService.unlock(testNode);
        
            this.securedLockService.lock(testNode, LockType.WRITE_LOCK, 2 * 86400, lt, null);
            
            // user who has ALL PERMISSIONS is able to unlock another's user lock
            TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
            this.securedLockService.unlock(testNode);
            
            this.nodeService.deleteNode(testNode);
        }
    }
}