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