001 /** 002 * Copyright 2005-2014 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 */ 016 package org.kuali.rice.krad.maintenance; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.kuali.rice.core.api.util.RiceKeyConstants; 020 import org.kuali.rice.kew.api.WorkflowDocument; 021 import org.kuali.rice.krad.exception.KualiExceptionIncident; 022 import org.kuali.rice.krad.exception.ValidationException; 023 import org.kuali.rice.krad.service.KRADServiceLocator; 024 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 025 import org.kuali.rice.krad.util.GlobalVariables; 026 import org.kuali.rice.krad.util.KRADConstants; 027 import org.kuali.rice.krad.util.UrlFactory; 028 029 import java.util.HashMap; 030 import java.util.Map; 031 import 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 */ 038 public 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 }