org.kuali.rice.krad.service.impl.PessimisticLockServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.rice.krad.service.impl.PessimisticLockServiceImpl.java

Source

/**
 * Copyright 2005-2014 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.rice.krad.service.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.kuali.rice.core.api.criteria.QueryByCriteria;
import org.kuali.rice.core.api.util.RiceConstants;
import org.kuali.rice.kim.api.KimConstants.PermissionNames;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.kim.api.permission.PermissionService;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;
import org.kuali.rice.kns.authorization.AuthorizationConstants;
import org.kuali.rice.krad.data.DataObjectService;
import org.kuali.rice.krad.document.Document;
import org.kuali.rice.krad.document.authorization.PessimisticLock;
import org.kuali.rice.krad.exception.AuthorizationException;
import org.kuali.rice.krad.exception.PessimisticLockingException;
import org.kuali.rice.krad.service.DataDictionaryService;
import org.kuali.rice.krad.service.LegacyDataAdapter;
import org.kuali.rice.krad.service.PessimisticLockService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.KRADConstants;
import org.kuali.rice.krad.util.KRADPropertyConstants;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.annotation.Transactional;

/**
 * Service implementation for pessimistic locking
 *
 * @author Kuali Rice Team (rice.collab@kuali.org)
 */
@Transactional
public class PessimisticLockServiceImpl implements PessimisticLockService {
    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
            .getLogger(PessimisticLockServiceImpl.class);

    protected DataObjectService dataObjectService;
    protected DataDictionaryService dataDictionaryService;
    protected PermissionService permissionService;
    protected PersonService personService;

    /**
      * @see org.kuali.rice.krad.service.PessimisticLockService#delete(java.lang.String)
      */
    @Override
    public void delete(String id) {
        if (StringUtils.isBlank(id)) {
            throw new IllegalArgumentException("An invalid blank id was passed to delete a Pessimistic Lock.");
        }
        PessimisticLock lock = dataObjectService.find(PessimisticLock.class, Long.valueOf(id));
        if (lock == null) {
            throw new IllegalArgumentException(
                    "Pessimistic Lock with id " + id + " cannot be found in the database.");
        }
        Person user = GlobalVariables.getUserSession().getPerson();
        if ((!lock.isOwnedByUser(user)) && (!isPessimisticLockAdminUser(user))) {
            throw new AuthorizationException(user.getName(), "delete", "Pessimistick Lock (id " + id + ")");
        }
        delete(lock);
    }

    private void delete(PessimisticLock lock) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Deleting lock: " + lock);
        }
        dataObjectService.delete(lock);
    }

    /**
     * @see org.kuali.rice.krad.service.PessimisticLockService#generateNewLock(String)
     */
    @Override
    public PessimisticLock generateNewLock(String documentNumber) {
        return generateNewLock(documentNumber, GlobalVariables.getUserSession().getPerson());
    }

    /**
     * @see org.kuali.rice.krad.service.PessimisticLockService#generateNewLock(java.lang.String)
     */
    @Override
    public PessimisticLock generateNewLock(String documentNumber, String lockDescriptor) {
        return generateNewLock(documentNumber, lockDescriptor, GlobalVariables.getUserSession().getPerson());
    }

    /**
     * @see org.kuali.rice.krad.service.PessimisticLockService#generateNewLock(java.lang.String, org.kuali.rice.kim.api.identity.Person)
     */
    @Override
    public PessimisticLock generateNewLock(String documentNumber, Person user) {
        return generateNewLock(documentNumber, PessimisticLock.DEFAULT_LOCK_DESCRIPTOR, user);
    }

    /**
     * @see org.kuali.rice.krad.service.PessimisticLockService#generateNewLock(java.lang.String, java.lang.String, org.kuali.rice.kim.api.identity.Person)
     */
    @Override
    public PessimisticLock generateNewLock(String documentNumber, String lockDescriptor, Person user) {
        PessimisticLock lock = new PessimisticLock(documentNumber, lockDescriptor, user,
                GlobalVariables.getUserSession());
        lock = save(lock);
        if (LOG.isDebugEnabled()) {
            LOG.debug("Generated new lock: " + lock);
        }
        return lock;
    }

    /**
     * @see org.kuali.rice.krad.service.PessimisticLockService#getPessimisticLocksForDocument(java.lang.String)
     */
    @Override
    public List<PessimisticLock> getPessimisticLocksForDocument(String documentNumber) {
        return new ArrayList<PessimisticLock>(
                dataObjectService
                        .findMatching(PessimisticLock.class, QueryByCriteria.Builder
                                .forAttribute(KRADPropertyConstants.DOCUMENT_NUMBER, documentNumber).build())
                        .getResults());
    }

    /**
     * @see org.kuali.rice.krad.service.PessimisticLockService#getPessimisticLocksForSession(java.lang.String)
     */
    @Override
    public List<PessimisticLock> getPessimisticLocksForSession(String sessionId) {
        return new ArrayList<PessimisticLock>(dataObjectService
                .findMatching(PessimisticLock.class,
                        QueryByCriteria.Builder.forAttribute(KRADPropertyConstants.SESSION_ID, sessionId).build())
                .getResults());
    }

    /**
     * @see org.kuali.rice.krad.service.PessimisticLockService#isPessimisticLockAdminUser(org.kuali.rice.kim.api.identity.Person)
     */
    @Override
    public boolean isPessimisticLockAdminUser(Person user) {
        return getPermissionService().isAuthorized(user.getPrincipalId(), KRADConstants.KNS_NAMESPACE,
                PermissionNames.ADMIN_PESSIMISTIC_LOCKING, Collections.<String, String>emptyMap());
    }

    /**
     * @see org.kuali.rice.krad.service.PessimisticLockService#releaseAllLocksForUser(java.util.List, org.kuali.rice.kim.api.identity.Person)
     */
    @Override
    public void releaseAllLocksForUser(List<PessimisticLock> locks, Person user) {
        for (Iterator<PessimisticLock> iterator = locks.iterator(); iterator.hasNext();) {
            PessimisticLock lock = iterator.next();
            if (lock.isOwnedByUser(user)) {
                try {
                    delete(lock);
                } catch (RuntimeException ex) {
                    if (ex.getCause() != null && ex.getCause().getClass()
                            .equals(LegacyDataAdapter.OPTIMISTIC_LOCK_OJB_EXCEPTION_CLASS)) {
                        LOG.warn(
                                "Suppressing Optimistic Lock Exception. Document Num: " + lock.getDocumentNumber());
                    } else {
                        throw ex;
                    }
                }
            }
        }
    }

    /**
     * @see org.kuali.rice.krad.service.PessimisticLockService#releaseAllLocksForUser(java.util.List, org.kuali.rice.kim.api.identity.Person, java.lang.String)
     */
    @Override
    public void releaseAllLocksForUser(List<PessimisticLock> locks, Person user, String lockDescriptor) {
        for (Iterator<PessimisticLock> iterator = locks.iterator(); iterator.hasNext();) {
            PessimisticLock lock = iterator.next();
            if ((lock.isOwnedByUser(user)) && (lockDescriptor.equals(lock.getLockDescriptor()))) {
                try {
                    delete(lock);
                } catch (RuntimeException ex) {
                    if (ex.getCause() != null && ex.getCause().getClass()
                            .equals(LegacyDataAdapter.OPTIMISTIC_LOCK_OJB_EXCEPTION_CLASS)) {
                        LOG.warn(
                                "Suppressing Optimistic Lock Exception. Document Num: " + lock.getDocumentNumber());
                    } else {
                        throw ex;
                    }
                }
            }
        }
    }

    /**
     * @see org.kuali.rice.krad.service.PessimisticLockService#save(org.kuali.rice.krad.document.authorization.PessimisticLock)
     */
    @Override
    public PessimisticLock save(PessimisticLock lock) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Saving lock: " + lock);
        }
        return dataObjectService.save(lock);
    }

    /**
     * @param document
     * @param user
     * @return Set of actions are permitted the given user on the given document
     */
    @Override
    public Set getDocumentActions(Document document, Person user, Set<String> documentActions) {
        if (documentActions.contains(KRADConstants.KUALI_ACTION_CAN_CANCEL)
                && !hasPreRouteEditAuthorization(document, user)) {
            documentActions.remove(KRADConstants.KUALI_ACTION_CAN_CANCEL);
        }
        if (documentActions.contains(KRADConstants.KUALI_ACTION_CAN_SAVE)
                && !hasPreRouteEditAuthorization(document, user)) {
            documentActions.remove(KRADConstants.KUALI_ACTION_CAN_SAVE);
        }
        if (documentActions.contains(KRADConstants.KUALI_ACTION_CAN_ROUTE)
                && !hasPreRouteEditAuthorization(document, user)) {
            documentActions.remove(KRADConstants.KUALI_ACTION_CAN_ROUTE);
        }
        if (documentActions.contains(KRADConstants.KUALI_ACTION_CAN_BLANKET_APPROVE)
                && !hasPreRouteEditAuthorization(document, user)) {
            documentActions.remove(KRADConstants.KUALI_ACTION_CAN_BLANKET_APPROVE);
        }
        return documentActions;
    }

    /**
     * This method checks to see that the given user has a lock on the document and return true if one is found.
     *
     * @param document - document to check
     * @param user - current user
     * @return true if the document is using Pessimistic Locking, the user has initiate authorization (see
     *         {@link #hasInitiateAuthorization(Document, Person)}), and the document has a lock owned by the given
     *         user. If the document is not using Pessimistic Locking the value returned will be that returned by
     *         {@link #hasInitiateAuthorization(Document, Person)}.
     */
    protected boolean hasPreRouteEditAuthorization(Document document, Person user) {
        if (document.getPessimisticLocks().isEmpty()) {
            return true;
        }
        for (Iterator<PessimisticLock> iterator = document.getPessimisticLocks().iterator(); iterator.hasNext();) {
            PessimisticLock lock = iterator.next();
            if (lock.isOwnedByUser(user)) {
                return true;
            }
        }
        return false;
    }

    protected boolean usesPessimisticLocking(Document document) {
        return getDataDictionaryService().getDataDictionary().getDocumentEntry(document.getClass().getName())
                .getUsePessimisticLocking();
    }

    /**
     * This method creates a new {@link PessimisticLock} when Workflow processing requires one
     *
     * @param document - the document to create the lock against and add the lock to
     * @see org.kuali.rice.kns.document.authorization.DocumentAuthorizer#establishWorkflowPessimisticLocking(org.kuali.rice.krad.document.Document)
     */
    @Override
    public void establishWorkflowPessimisticLocking(Document document) {
        PessimisticLock lock = createNewPessimisticLock(document, new HashMap(),
                getWorkflowPessimisticLockOwnerUser());
        document.addPessimisticLock(lock);
    }

    /**
     * This method releases locks created via the {@link #establishWorkflowPessimisticLocking(Document)} method for the given document
     *
     * @param document - document to release locks from
     * @see org.kuali.rice.kns.document.authorization.DocumentAuthorizer#releaseWorkflowPessimisticLocking(org.kuali.rice.krad.document.Document)
     */
    @Override
    public void releaseWorkflowPessimisticLocking(Document document) {
        releaseAllLocksForUser(document.getPessimisticLocks(), getWorkflowPessimisticLockOwnerUser());
        document.refreshPessimisticLocks();
    }

    /**
     * This method identifies the user that should be used to create and clear {@link PessimisticLock} objects required by
     * Workflow.<br>
     * <br>
     * The default is the Kuali system user defined by {@link RiceConstants#SYSTEM_USER}. This method can be overriden by
     * implementing documents if another user is needed.
     *
     * @return a valid {@link Person} object
     */
    protected Person getWorkflowPessimisticLockOwnerUser() {
        String networkId = KRADConstants.SYSTEM_USER;
        return getPersonService().getPersonByPrincipalName(networkId);
    }

    /**
     * This implementation will check the given document, editMode map, and user object to verify Pessimistic Locking. If the
     * given edit mode map contains an 'entry type' edit mode then the system will check the locks already in existence on
     * the document. If a valid lock for the given user is found the system will return the given edit mode map. If a valid
     * lock is found but is owned by another user the edit mode map returned will have any 'entry type' edit modes removed. If the
     * given document has no locks and the edit mode map passed in has at least one 'entry type' mode then a new
     * {@link PessimisticLock} object will be created and set on the document for the given user.<br>
     * <br>
     * NOTE: This method is only called if the document uses pessimistic locking as described in the data dictionary file.
     *
     * @see org.kuali.rice.kns.document.authorization.DocumentAuthorizer#establishLocks(org.kuali.rice.krad.document.Document,
     *      java.util.Map, org.kuali.rice.kim.api.identity.Person)
     */
    @Override
    public Map establishLocks(Document document, Map editMode, Person user) {
        Map editModeMap = new HashMap();
        // givenUserLockDescriptors is a list of lock descriptors currently held on the document by the given user
        List<String> givenUserLockDescriptors = new ArrayList<String>();
        // lockDescriptorUsers is a map with lock descriptors as keys and users other than the given user who hold a lock of each descriptor
        Map<String, Set<Person>> lockDescriptorUsers = new HashMap<String, Set<Person>>();

        // build the givenUserLockDescriptors set and the lockDescriptorUsers map
        for (PessimisticLock lock : document.getPessimisticLocks()) {
            if (lock.isOwnedByUser(user)) {
                // lock is owned by given user
                givenUserLockDescriptors.add(lock.getLockDescriptor());
            } else {
                // lock is not owned by the given user
                if (!lockDescriptorUsers.containsKey(lock.getLockDescriptor())) {
                    lockDescriptorUsers.put(lock.getLockDescriptor(), new HashSet<Person>());
                }
                lockDescriptorUsers.get(lock.getLockDescriptor()).add(lock.getOwnedByUser());
            }
        }

        // verify that no locks held by current user exist for any other user
        for (String givenUserLockDescriptor : givenUserLockDescriptors) {
            if ((lockDescriptorUsers.containsKey(givenUserLockDescriptor))
                    && (lockDescriptorUsers.get(givenUserLockDescriptor).size() > 0)) {
                Set<Person> users = lockDescriptorUsers.get(givenUserLockDescriptor);
                if ((users.size() != 1) || (!getWorkflowPessimisticLockOwnerUser().getPrincipalId()
                        .equals(users.iterator().next().getPrincipalId()))) {
                    String descriptorText = (document.useCustomLockDescriptors())
                            ? " using lock descriptor '" + givenUserLockDescriptor + "'"
                            : "";
                    String errorMsg = "Found an invalid lock status on document number "
                            + document.getDocumentNumber() + "with current user and other user both having locks"
                            + descriptorText + " concurrently";
                    LOG.debug(errorMsg);
                    throw new PessimisticLockingException(errorMsg);
                }
            }
        }

        // check to see if the given user has any locks in the system at all
        if (givenUserLockDescriptors.isEmpty()) {
            // given user has no locks... check for other user locks
            if (lockDescriptorUsers.isEmpty()) {
                // no other user has any locks... set up locks for given user if user has edit privileges
                if (isLockRequiredByUser(document, editMode, user)) {
                    document.addPessimisticLock(createNewPessimisticLock(document, editMode, user));
                }
                editModeMap.putAll(editMode);
            } else {
                // at least one other user has at least one other lock... adjust edit mode for read only
                if (document.useCustomLockDescriptors()) {
                    // check to see if the custom lock descriptor is already in use
                    String customLockDescriptor = document.getCustomLockDescriptor(user);
                    if (lockDescriptorUsers.containsKey(customLockDescriptor)) {
                        // at least one other user has this descriptor locked... remove editable edit modes
                        editModeMap = getEditModeWithEditableModesRemoved(editMode);
                    } else {
                        // no other user has a lock with this descriptor
                        if (isLockRequiredByUser(document, editMode, user)) {
                            document.addPessimisticLock(createNewPessimisticLock(document, editMode, user));
                        }
                        editModeMap.putAll(editMode);
                    }
                } else {
                    editModeMap = getEditModeWithEditableModesRemoved(editMode);
                }
            }
        } else {
            // given user already has at least one lock descriptor
            if (document.useCustomLockDescriptors()) {
                // get the custom lock descriptor and check to see if if the given user has a lock with that descriptor
                String customLockDescriptor = document.getCustomLockDescriptor(user);
                if (givenUserLockDescriptors.contains(customLockDescriptor)) {
                    // user already has lock that is required
                    editModeMap.putAll(editMode);
                } else {
                    // user does not have lock for descriptor required
                    if (lockDescriptorUsers.containsKey(customLockDescriptor)) {
                        // another user has the lock descriptor that the given user requires... disallow lock and alter edit modes to have read only
                        editModeMap = getEditModeWithEditableModesRemoved(editMode);
                    } else {
                        // no other user has a lock with this descriptor... check if this user needs a lock
                        if (isLockRequiredByUser(document, editMode, user)) {
                            document.addPessimisticLock(createNewPessimisticLock(document, editMode, user));
                        }
                        editModeMap.putAll(editMode);
                    }
                }
            } else {
                // user already has lock and no descriptors are being used... use the existing edit modes
                editModeMap.putAll(editMode);
            }
        }

        return editModeMap;
    }

    /**
     * This method is used to check if the given parameters warrant a new lock to be created for the given user. This method
     * utilizes the {@link #isEntryEditMode(java.util.Map.Entry)} method.
     *
     * @param document -
     *            document to verify lock creation against
     * @param editMode -
     *            edit modes list to check for 'entry type' edit modes
     * @param user -
     *            user the lock will be 'owned' by
     * @return true if the given edit mode map has at least one 'entry type' edit mode... false otherwise
     */
    protected boolean isLockRequiredByUser(Document document, Map editMode, Person user) {
        // check for entry edit mode
        for (Iterator iterator = editMode.entrySet().iterator(); iterator.hasNext();) {
            Map.Entry entry = (Map.Entry) iterator.next();
            if (isEntryEditMode(entry)) {
                return true;
            }
        }
        return false;
    }

    /**
      * This method is used to remove edit modes from the given map that allow the user to edit data on the document. This
      * method utilizes the {@link #isEntryEditMode(java.util.Map.Entry)} method to identify if an edit mode is defined as an
      * 'entry type' edit mode. It also uses the {@link #getEntryEditModeReplacementMode(java.util.Map.Entry)} method to replace
      * any 'entry type' edit modes it finds.
      *
      * @param currentEditMode -
      *            current set of edit modes the user has assigned to them
      * @return an adjusted edit mode map where 'entry type' edit modes have been removed or replaced using the
      *         {@link #getEntryEditModeReplacementMode} method
      */
    protected Map getEditModeWithEditableModesRemoved(Map currentEditMode) {
        Map editModeMap = new HashMap();
        for (Iterator iterator = currentEditMode.entrySet().iterator(); iterator.hasNext();) {
            Map.Entry entry = (Map.Entry) iterator.next();
            if (isEntryEditMode(entry)) {
                editModeMap.putAll(getEntryEditModeReplacementMode(entry));
            } else {
                editModeMap.put(entry.getKey(), entry.getValue());
            }
        }
        return editModeMap;
    }

    /**
     * This method is used to check if the given {@link Map.Entry} is an 'entry type' edit mode and that the value is set to
     * signify that this user has that edit mode available to them
     *
     * @param entry -
     *            the {@link Map.Entry} object that contains an edit mode such as the ones returned but
     *            {@link #getEditMode(Document, Person)}
     * @return true if the given entry has a key signifying an 'entry type' edit mode and the value is equal to
     *         {@link #EDIT_MODE_DEFAULT_TRUE_VALUE}... false if not
     */
    protected boolean isEntryEditMode(Map.Entry entry) {
        // check for FULL_ENTRY edit mode set to default true value
        if (AuthorizationConstants.EditMode.FULL_ENTRY.equals(entry.getKey())) {
            String fullEntryEditModeValue = (String) entry.getValue();
            return (StringUtils.equalsIgnoreCase(KRADConstants.KUALI_DEFAULT_TRUE_VALUE, fullEntryEditModeValue));
        }
        return false;
    }

    /**
     * This method is used to return values needed to replace the given 'entry type' edit mode {@link Map.Entry} with one that will not allow the user to enter data on the document
     *
     * @param entry - the current 'entry type' edit mode to replace
     * @return a Map of edit modes that will be used to replace this edit mode (represented by the given entry parameter)
     */
    protected Map getEntryEditModeReplacementMode(Map.Entry entry) {
        Map editMode = new HashMap();
        editMode.put(AuthorizationConstants.EditMode.VIEW_ONLY, KRADConstants.KUALI_DEFAULT_TRUE_VALUE);
        return editMode;
    }

    /**
     * This method creates a new {@link PessimisticLock} object using the given document and user. If the document's
     * useCustomLockDescriptors() method returns true then the new lock will also have a custom lock descriptor
     * value set to the return value of the document's getCustomLockDescriptor(Person) method.
     *
     * @param document -
     *            document to place the lock on
     * @param editMode -
     *            current edit modes for given user
     * @param user -
     *            user who will 'own' the new lock object
     * @return the newly created lock object
     */
    protected PessimisticLock createNewPessimisticLock(Document document, Map editMode, Person user) {
        if (document.useCustomLockDescriptors()) {
            return generateNewLock(document.getDocumentNumber(), document.getCustomLockDescriptor(user), user);
        } else {
            return generateNewLock(document.getDocumentNumber(), user);
        }
    }

    public PersonService getPersonService() {
        if (personService == null) {
            personService = KimApiServiceLocator.getPersonService();
        }
        return personService;
    }

    public DataDictionaryService getDataDictionaryService() {
        return dataDictionaryService;
    }

    @Required
    public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
        this.dataDictionaryService = dataDictionaryService;
    }

    public PermissionService getPermissionService() {
        if (permissionService == null) {
            permissionService = KimApiServiceLocator.getPermissionService();
        }
        return permissionService;
    }

    @Required
    public void setDataObjectService(DataObjectService dataObjectService) {
        this.dataObjectService = dataObjectService;
    }
}