001/**
002 * Copyright 2005-2016 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.krad.maintenance;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.util.RiceKeyConstants;
020import org.kuali.rice.kew.api.WorkflowDocument;
021import org.kuali.rice.krad.exception.KualiExceptionIncident;
022import org.kuali.rice.krad.exception.ValidationException;
023import org.kuali.rice.krad.service.KRADServiceLocator;
024import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
025import org.kuali.rice.krad.util.GlobalVariables;
026import org.kuali.rice.krad.util.KRADConstants;
027import org.kuali.rice.krad.util.UrlFactory;
028
029import java.util.HashMap;
030import java.util.Map;
031import java.util.Properties;
032
033/**
034 * Provides static utility methods for use within the maintenance framework
035 *
036 * @author Kuali Rice Team (rice.collab@kuali.org)
037 */
038public class MaintenanceUtils {
039    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintenanceUtils.class);
040
041    /**
042     * Determines if there is another maintenance document that has a lock on the same key as the given document, and
043     * therefore will block the maintenance document from being submitted
044     *
045     * @param document - maintenance document instance to check locking for
046     * @param throwExceptionIfLocked - indicates if an exception should be thrown in the case of found locking document,
047     * if false only an error will be added
048     */
049    public static void checkForLockingDocument(MaintenanceDocument document, boolean throwExceptionIfLocked) {
050        LOG.info("starting checkForLockingDocument (by MaintenanceDocument)");
051
052        // get the docHeaderId of the blocking docs, if any are locked and blocking
053        //String blockingDocId = getMaintenanceDocumentService().getLockingDocumentId(document);
054        String blockingDocId = document.getNewMaintainableObject().getLockingDocumentId();
055        checkDocumentBlockingDocumentId(blockingDocId, throwExceptionIfLocked);
056    }
057
058    public static void checkDocumentBlockingDocumentId(String blockingDocId, boolean throwExceptionIfLocked) {
059        // if we got nothing, then no docs are blocking, and we're done
060        if (StringUtils.isBlank(blockingDocId)) {
061            return;
062        }
063
064        if (MaintenanceUtils.LOG.isInfoEnabled()) {
065            MaintenanceUtils.LOG.info("Locking document found:  docId = " + blockingDocId + ".");
066        }
067
068        // load the blocking locked document
069        WorkflowDocument lockedDocument = null;
070        try {
071            // need to perform this check to prevent an exception from being thrown by the
072            // createWorkflowDocument call - the throw itself causes transaction rollback problems to
073            // occur, even though the exception would be caught here
074            if (KRADServiceLocatorWeb.getWorkflowDocumentService().workflowDocumentExists(blockingDocId)) {
075                lockedDocument = KRADServiceLocatorWeb.getWorkflowDocumentService()
076                        .loadWorkflowDocument(blockingDocId, GlobalVariables.getUserSession().getPerson());
077            }
078        } catch (Exception ex) {
079            // clean up the lock and notify the admins
080            MaintenanceUtils.LOG.error("Unable to retrieve locking document specified in the maintenance lock table: " +
081                    blockingDocId, ex);
082
083            cleanOrphanLocks(blockingDocId, ex);
084            return;
085        }
086        if (lockedDocument == null) {
087            MaintenanceUtils.LOG.warn("Locking document header for " + blockingDocId + "came back null.");
088            cleanOrphanLocks(blockingDocId, null);
089        }
090
091        // if we can ignore the lock (see method notes), then exit cause we're done
092        if (lockCanBeIgnored(lockedDocument)) {
093            return;
094        }
095
096        // build the link URL for the blocking document
097        Properties parameters = new Properties();
098        parameters.put(KRADConstants.PARAMETER_DOC_ID, blockingDocId);
099        parameters.put(KRADConstants.PARAMETER_COMMAND, KRADConstants.METHOD_DISPLAY_DOC_SEARCH_VIEW);
100        String blockingUrl = UrlFactory.parameterizeUrl(
101                KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(
102                        KRADConstants.WORKFLOW_URL_KEY) +
103                        "/" + KRADConstants.DOC_HANDLER_ACTION, parameters);
104        if (MaintenanceUtils.LOG.isDebugEnabled()) {
105            MaintenanceUtils.LOG.debug("blockingUrl = '" + blockingUrl + "'");
106            MaintenanceUtils.LOG.debug("Maintenance record: " + lockedDocument.getApplicationDocumentId() + "is locked.");
107        }
108        String[] errorParameters = {blockingUrl, blockingDocId};
109
110        // If specified, add an error to the ErrorMap and throw an exception; otherwise, just add a warning to the ErrorMap instead.
111        if (throwExceptionIfLocked) {
112            // post an error about the locked document
113            GlobalVariables.getMessageMap()
114                    .putError(KRADConstants.GLOBAL_ERRORS, RiceKeyConstants.ERROR_MAINTENANCE_LOCKED, errorParameters);
115            throw new ValidationException("Maintenance Record is locked by another document.");
116        } else {
117            // Post a warning about the locked document.
118            GlobalVariables.getMessageMap()
119                    .putWarning(KRADConstants.GLOBAL_MESSAGES, RiceKeyConstants.WARNING_MAINTENANCE_LOCKED,
120                            errorParameters);
121        }
122    }
123
124    /**
125     * Guesses whether the current user should be allowed to change a document even though it is locked. It
126     * probably should use Authorization instead? See KULNRVSYS-948
127     *
128     * @param lockedDocument
129     * @return
130     * @throws org.kuali.rice.kew.api.exception.WorkflowException
131     *
132     */
133    private static boolean lockCanBeIgnored(WorkflowDocument lockedDocument) {
134        // TODO: implement real authorization for Maintenance Document Save/Route - KULNRVSYS-948
135        if (lockedDocument == null) {
136            return true;
137        }
138
139        // get the user-id. if no user-id, then we can do this test, so exit
140        String userId = GlobalVariables.getUserSession().getPrincipalId().trim();
141        if (StringUtils.isBlank(userId)) {
142            return false; // dont bypass locking
143        }
144
145        // if the current user is not the initiator of the blocking document
146        if (!userId.equalsIgnoreCase(lockedDocument.getInitiatorPrincipalId().trim())) {
147            return false;
148        }
149
150        // if the blocking document hasn't been routed, we can ignore it
151        return lockedDocument.isInitiated();
152    }
153
154    protected static void cleanOrphanLocks(String lockingDocumentNumber, Exception workflowException) {
155        // put a try/catch around the whole thing - the whole reason we are doing this is to prevent data errors
156        // from stopping a document
157        try {
158            // delete the locks for this document since it does not seem to exist
159            KRADServiceLocatorWeb.getMaintenanceDocumentService().deleteLocks(lockingDocumentNumber);
160            // notify the incident list
161            Map<String, String> parameters = new HashMap<String, String>(1);
162            parameters.put(KRADConstants.PARAMETER_DOC_ID, lockingDocumentNumber);
163            KualiExceptionIncident kei = KRADServiceLocatorWeb.getKualiExceptionIncidentService()
164                    .getExceptionIncident(workflowException, parameters);
165            KRADServiceLocatorWeb.getKualiExceptionIncidentService().report(kei);
166        } catch (Exception ex) {
167            MaintenanceUtils.LOG.error("Unable to delete and notify upon locking document retrieval failure.", ex);
168        }
169    }
170
171    public static boolean isMaintenanceDocumentCreatingNewRecord(String maintenanceAction) {
172        if (KRADConstants.MAINTENANCE_EDIT_ACTION.equalsIgnoreCase(maintenanceAction)) {
173            return false;
174        } else if (KRADConstants.MAINTENANCE_NEWWITHEXISTING_ACTION.equalsIgnoreCase(maintenanceAction)) {
175            return false;
176        } else if (KRADConstants.MAINTENANCE_DELETE_ACTION.equalsIgnoreCase(maintenanceAction)) {
177            return false;
178        } else if (KRADConstants.MAINTENANCE_NEW_ACTION.equalsIgnoreCase(maintenanceAction)) {
179            return true;
180        } else if (KRADConstants.MAINTENANCE_COPY_ACTION.equalsIgnoreCase(maintenanceAction)) {
181            return true;
182        } else {
183            return true;
184        }
185    }
186}