001/**
002 * Copyright 2005-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kns.service.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.junit.Test;
020import org.kuali.rice.kew.api.exception.WorkflowException;
021import org.kuali.rice.kim.api.KimConstants.PermissionNames;
022import org.kuali.rice.kim.api.identity.Person;
023import org.kuali.rice.kim.api.services.KimApiServiceLocator;
024import org.kuali.rice.kns.KNSTestCase;
025import org.kuali.rice.kns.authorization.AuthorizationConstants;
026import org.kuali.rice.krad.UserSession;
027import org.kuali.rice.krad.document.Document;
028import org.kuali.rice.krad.document.authorization.PessimisticLock;
029import org.kuali.rice.krad.exception.AuthorizationException;
030import org.kuali.rice.krad.maintenance.MaintenanceDocument;
031import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
032import org.kuali.rice.krad.service.PessimisticLockService;
033import org.kuali.rice.krad.service.impl.PessimisticLockServiceImpl;
034import org.kuali.rice.krad.test.KRADTestCase;
035import org.kuali.rice.krad.test.document.AccountRequestDocument;
036import org.kuali.rice.krad.test.document.AccountRequestDocument2;
037import org.kuali.rice.krad.util.GlobalVariables;
038import org.kuali.rice.krad.util.KRADConstants;
039import org.kuali.rice.krad.util.KRADPropertyConstants;
040import org.kuali.rice.kns.maintainable.AccountType2MaintainableImpl;
041import org.kuali.rice.test.BaselineTestCase;
042import org.kuali.rice.test.data.UnitTestData;
043import org.kuali.rice.test.data.UnitTestSql;
044
045import java.io.Serializable;
046import java.util.Arrays;
047import java.util.Collections;
048import java.util.HashMap;
049import java.util.HashSet;
050import java.util.List;
051import java.util.Map;
052import java.util.Set;
053
054import static org.junit.Assert.*;
055
056/**
057 * PessimisticLockServiceTest tests {@link PessimisticLockServiceImpl} for maintainable
058 *
059 * @author Kuali Rice Team (rice.collab@kuali.org)
060 */
061@BaselineTestCase.BaselineMode(BaselineTestCase.Mode.NONE)
062public class PessimisticLockServiceTest extends KNSTestCase {
063
064    String sessionId = "ad4d6c83-4d0f-4309-a528-c2f81ec00395";
065
066    @Override
067    public void setUp() throws Exception {
068        super.setUp();
069        GlobalVariables.setUserSession(new UserSession("quickstart"));
070        GlobalVariables.getUserSession().setKualiSessionId(sessionId);
071    }
072
073    /**
074     * tests the PessimisticLockService's ability to establish pessimistic locks for maintenance documents (via maintainables) that
075     * support custom lock descriptors
076     * 
077     * @throws Exception
078     */
079    @Test
080    public void testPessimisticLockingWithCustomMaintainableLockDescriptors() throws Exception {
081        MaintenanceDocument maintDoc = (MaintenanceDocument) KRADServiceLocatorWeb.getDocumentService().getNewDocument("AccountType2MaintenanceDocument");
082        assertTrue("The AccountType2MaintenanceDocument should be using pessimistic locking", KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary(
083                        ).getDocumentEntry(maintDoc.getNewMaintainableObject().getDataObjectClass().getSimpleName() + "MaintenanceDocument").getUsePessimisticLocking());
084        assertTrue("The AccountType2MaintenanceDocument should be using custom lock descriptors", maintDoc.useCustomLockDescriptors());
085        assertTrue("The AccountType2MaintenanceDocument's new maintainable uses the wrong class",
086                        maintDoc.getNewMaintainableObject() instanceof AccountType2MaintainableImpl);
087        AccountType2MaintainableImpl newMaint = (AccountType2MaintainableImpl) maintDoc.getNewMaintainableObject();
088        assertTrue("The AccountType2MaintainableImpl should be using custom lock descriptors", newMaint.useCustomLockDescriptors());
089        
090        // Perform the custom lock descriptor unit testing operations.
091        assertCustomLockDescriptorsAreWorking(maintDoc, AccountType2MaintainableImpl.ACCT_TYPE_2_MAINT_FIELDS_TO_EDIT,
092                        AccountType2MaintainableImpl.EDIT_CODE_ONLY, AccountType2MaintainableImpl.EDIT_NAME_ONLY);
093    }
094    
095    /**
096     * A convenience method for testing the custom lock descriptors of documents (and on the maintainables of maintenance documents).
097     *
098     * @param testDoc The document to test pessimistic locking on (or the maintenance document with maintainables to test on).
099     * @param LOCK_KEY The UserSession object key to use for storing the lock descriptor's key.
100     * @param LOCK_VALUE1 One possible object to store in a UserSession for generating lock descriptors on the testDoc.
101     * @param LOCK_VALUE2 Another possible object to store in a UserSession for generating lock descriptors on the testDoc.
102     *
103     * @throws Exception
104     */
105    private void assertCustomLockDescriptorsAreWorking(Document testDoc, final String LOCK_KEY, final Serializable LOCK_VALUE1,
106                final Serializable LOCK_VALUE2) throws Exception {
107        PessimisticLockService lockService = KRADServiceLocatorWeb.getPessimisticLockService();
108
109        // Have "quickstart" establish a pessimistic lock on the document by using a custom lock descriptor that only locks part of the document.
110        UserSession quickstartSession = new UserSession("quickstart");
111        Person[] allPersons = { quickstartSession.getPerson(), null };
112        Map<String,String> editMode = new HashMap<String,String>();
113        editMode.put(AuthorizationConstants.EditMode.FULL_ENTRY, KRADConstants.KUALI_DEFAULT_TRUE_VALUE);
114        GlobalVariables.getUserSession().addObject(LOCK_KEY, LOCK_VALUE1);
115        String[] allDescriptors = { testDoc.getCustomLockDescriptor(quickstartSession.getPerson()), null };
116                assertNotNull("The document should have generated a custom lock descriptor", allDescriptors[0]);
117        Map <?,?> finalModes = lockService.establishLocks(testDoc, editMode, quickstartSession.getPerson());
118
119        // Verify that the lock was actually established and that the expected custom lock descriptor was used.
120        assertCorrectLocksAreInPlace(true, finalModes, 1, testDoc.getPessimisticLocks(), allPersons, allDescriptors);
121
122        // Attempt to establish the same lock again, which should change nothing since "quickstart" already has the lock.
123        editMode = new HashMap<String,String>();
124        editMode.put(AuthorizationConstants.EditMode.FULL_ENTRY, KRADConstants.KUALI_DEFAULT_TRUE_VALUE);
125        GlobalVariables.getUserSession().addObject(LOCK_KEY, LOCK_VALUE1);
126        lockService.establishLocks(testDoc, editMode, quickstartSession.getPerson());
127        assertCorrectLocksAreInPlace(false, null, 1, testDoc.getPessimisticLocks(), allPersons, allDescriptors);
128
129        // Now check to make sure that a different user (such as "admin") cannot establish a lock using the same lock descriptor.
130        UserSession adminSession = new UserSession("admin");
131        editMode = new HashMap<String,String>();
132        editMode.put(AuthorizationConstants.EditMode.FULL_ENTRY, KRADConstants.KUALI_DEFAULT_TRUE_VALUE);
133        GlobalVariables.getUserSession().addObject(LOCK_KEY, LOCK_VALUE1);
134        assertEquals("The document should have generated the same lock descriptors for both 'quickstart' and 'admin'",
135                        allDescriptors[0], testDoc.getCustomLockDescriptor(adminSession.getPerson()));
136        finalModes = lockService.establishLocks(testDoc, editMode, adminSession.getPerson());
137        assertCorrectLocksAreInPlace(false, finalModes, 1, testDoc.getPessimisticLocks(), allPersons, allDescriptors);
138
139        // Ensure that "admin" can establish a lock that has a different lock descriptor.
140        allPersons[1] = adminSession.getPerson();
141        editMode = new HashMap<String,String>();
142        editMode.put(AuthorizationConstants.EditMode.FULL_ENTRY, KRADConstants.KUALI_DEFAULT_TRUE_VALUE);
143        GlobalVariables.getUserSession().addObject(LOCK_KEY, LOCK_VALUE2);
144        allDescriptors[1] = testDoc.getCustomLockDescriptor(adminSession.getPerson());
145        assertNotNull("The document should have generated a custom lock descriptor", allDescriptors[1]);
146        assertNotSame("'quickstart' and 'admin' should have different custom lock descriptors now", allDescriptors[0], allDescriptors[1]);
147        finalModes = lockService.establishLocks(testDoc, editMode, adminSession.getPerson());
148        assertCorrectLocksAreInPlace(true, finalModes, 2, testDoc.getPessimisticLocks(), allPersons, allDescriptors);
149
150        // Verify that "quickstart" cannot acquire the lock owned by "admin".
151        editMode = new HashMap<String,String>();
152        editMode.put(AuthorizationConstants.EditMode.FULL_ENTRY, KRADConstants.KUALI_DEFAULT_TRUE_VALUE);
153        GlobalVariables.getUserSession().addObject(LOCK_KEY, LOCK_VALUE2);
154        lockService.establishLocks(testDoc, editMode, quickstartSession.getPerson());
155        assertCorrectLocksAreInPlace(false, null, 2, testDoc.getPessimisticLocks(), allPersons, allDescriptors);
156
157        // After "admin" releases his lock, check to make sure that "quickstart" can now acquire it.
158        lockService.releaseAllLocksForUser(testDoc.getPessimisticLocks(), allPersons[1], allDescriptors[1]);
159        testDoc.refreshPessimisticLocks();
160        assertCorrectLocksAreInPlace(false, null, 1, testDoc.getPessimisticLocks(), allPersons, allDescriptors);
161        allPersons[1] = allPersons[0];
162        editMode = new HashMap<String,String>();
163        editMode.put(AuthorizationConstants.EditMode.FULL_ENTRY, KRADConstants.KUALI_DEFAULT_TRUE_VALUE);
164        GlobalVariables.getUserSession().addObject(LOCK_KEY, LOCK_VALUE2);
165        finalModes = lockService.establishLocks(testDoc, editMode, quickstartSession.getPerson());
166        assertCorrectLocksAreInPlace(true, finalModes, 2, testDoc.getPessimisticLocks(), allPersons, allDescriptors);
167
168        // Release all the locks when done.
169        GlobalVariables.getUserSession().removeObject(LOCK_KEY);
170        lockService.releaseAllLocksForUser(testDoc.getPessimisticLocks(), allPersons[0]);
171        testDoc.refreshPessimisticLocks();
172        assertTrue("There should not be any pessimistic locks present on the document", testDoc.getPessimisticLocks().isEmpty());
173    }
174
175    /**
176     * A convenience method for checking to ensure that the proper pessimistic locks are in place.
177     *
178     * @param latestUserHasFullEntry Indicates if the map returned by PessimisticLockService.establishLocks should have a true "fullEntry" parameter.
179     * @param finalModes The map returned by the call to PessimisticLockService.establishLocks. This parameter can be null if checking it is not needed.
180     * @param expectedLockQuantity The expected number of pessimistic locks.
181     * @param pessimisticLocks The list of pessimistic locks to check for proper quantity and proper state.
182     * @param expectedOwners The users who are expected to own the corresponding locks in the previous list.
183     * @param expectedDescriptors The expected lock descriptors for the corresponding locks in the other list. This parameter can be set to null if
184     * the pessimistic locks are not using custom lock descriptors or if custom lock descriptors are not the concern of the test.
185     * @throws Exception
186     */
187    private void assertCorrectLocksAreInPlace(boolean latestUserHasFullEntry, Map<?,?> finalModes, int expectedLockQuantity,
188                List<PessimisticLock> pessimisticLocks, Person[] expectedOwners, String[] expectedDescriptors) throws Exception {
189        // Ensure that the last user to attempt to establish locks has the expected finalModes entry (or lack of it).
190        if (finalModes != null) {
191                assertEquals("The last user that tried to establish locks does not have the expected status on their full entry privileges",
192                                latestUserHasFullEntry, StringUtils.equalsIgnoreCase(KRADConstants.KUALI_DEFAULT_TRUE_VALUE, (String)(finalModes.get(
193                    AuthorizationConstants.EditMode.FULL_ENTRY))));
194        }
195        // Ensure that the expected number of locks are present.
196        assertEquals("The wrong number of pessimistic locks are in place", expectedLockQuantity, pessimisticLocks.size());
197        // Verify that each lock has the expected owners.
198        for (int i = pessimisticLocks.size() - 1; i > -1; i--) {
199                assertTrue("The lock at index " + i + " did not have the expected owner of " + expectedOwners[i].getPrincipalName(),
200                        pessimisticLocks.get(i).isOwnedByUser(expectedOwners[i]));
201                if (expectedDescriptors != null) {
202                        assertTrue("The lock at index " + i + " did not have the expected lock descriptor of " + expectedDescriptors[i],
203                                        pessimisticLocks.get(i).getLockDescriptor().equals(expectedDescriptors[i]));
204                }
205        }
206    }
207}