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.service.impl; 017 018import org.apache.commons.lang.StringUtils; 019import org.apache.log4j.Logger; 020import org.kuali.rice.core.api.exception.RiceRuntimeException; 021import org.kuali.rice.krad.bo.AdHocRoutePerson; 022import org.kuali.rice.krad.bo.AdHocRouteWorkgroup; 023import org.kuali.rice.krad.document.Document; 024import org.kuali.rice.krad.document.TransactionalDocument; 025import org.kuali.rice.krad.maintenance.MaintenanceDocument; 026import org.kuali.rice.krad.rules.MaintenanceDocumentRuleBase; 027import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase; 028import org.kuali.rice.krad.rules.rule.BusinessRule; 029import org.kuali.rice.krad.rules.rule.event.AddAdHocRoutePersonEvent; 030import org.kuali.rice.krad.rules.rule.event.AddAdHocRouteWorkgroupEvent; 031import org.kuali.rice.krad.rules.rule.event.DocumentEvent; 032import org.kuali.rice.krad.rules.rule.event.RuleEvent; 033import org.kuali.rice.krad.service.DataDictionaryService; 034import org.kuali.rice.krad.service.DocumentDictionaryService; 035import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 036import org.kuali.rice.krad.service.KualiRuleService; 037import org.kuali.rice.krad.uif.component.MethodInvokerConfig; 038import org.kuali.rice.krad.util.GlobalVariables; 039import org.kuali.rice.krad.util.KRADConstants; 040import org.kuali.rice.krad.util.MessageMap; 041 042import java.util.ArrayList; 043import java.util.List; 044 045/** 046 * Represents a rule evaluator for Kuali. This class is to be used for evaluating business rule checks. The class 047 * defines one method right now - applyRules() which takes in a Document and a DocumentEvent and does the proper 048 * business rule checks based on the context of the event and the document type 049 * 050 * @author Kuali Rice Team (rice.collab@kuali.org) 051 */ 052public class KualiRuleServiceImpl implements KualiRuleService { 053 private static final Logger LOG = Logger.getLogger(KualiRuleServiceImpl.class); 054 055 private DocumentDictionaryService documentDictionaryService; 056 private DataDictionaryService dataDictionaryService; 057 058 /** 059 * @see org.kuali.rice.krad.service.KualiRuleService#applyRules(org.kuali.rice.krad.rules.rule.event.DocumentEvent) 060 */ 061 public boolean applyRules(DocumentEvent event) { 062 if (event == null) { 063 throw new IllegalArgumentException("invalid (null) event"); 064 } 065 066 event.validate(); 067 if (LOG.isDebugEnabled()) { 068 LOG.debug("calling applyRules for event " + event); 069 } 070 071 BusinessRule rule = getBusinessRulesInstance(event.getDocument(), event.getRuleInterfaceClass()); 072 073 boolean success = true; 074 if (rule != null) { 075 if (LOG.isDebugEnabled()) { 076 LOG.debug("processing " + event.getName() + " with rule " + rule.getClass().getName()); 077 } 078 increaseErrorPath(event.getErrorPathPrefix()); 079 080 // get any child events and apply rules 081 List<RuleEvent> events = event.generateEvents(); 082 for (RuleEvent generatedEvent : events) { 083 success &= applyRules((DocumentEvent) generatedEvent); 084 } 085 086 // now call the event rule method 087 if (StringUtils.isNotBlank(event.getRuleMethodName())) { 088 success &= invokeBusinessRuleMethod(rule, event); 089 } else { 090 success &= event.invokeRuleMethod(rule); 091 } 092 093 decreaseErrorPath(event.getErrorPathPrefix()); 094 095 // report failures 096 if (!success) { 097 if (LOG.isDebugEnabled()) { // NO, this is not a type - only log if in debug mode - this is not an error in production 098 LOG.debug(event.getName() + " businessRule " + rule.getClass().getName() + " failed"); 099 } 100 } else { 101 if (LOG.isDebugEnabled()) { 102 LOG.debug("processed " + event.getName() + " for rule " + rule.getClass().getName()); 103 } 104 } 105 106 } 107 return success; 108 } 109 110 /** 111 * local helper method to invoke the business rule method 112 * 113 * @param rule the business rule class that the method to invoke belongs to 114 * @param event the document event the rule applies to 115 * @return a boolean to indicate whether the method invocation was a succes or not 116 */ 117 public boolean invokeBusinessRuleMethod(BusinessRule rule, DocumentEvent event) { 118 boolean success = true; 119 120 String methodName = event.getRuleMethodName(); 121 MethodInvokerConfig methodInvoker = new MethodInvokerConfig(); 122 methodInvoker.setTargetClass(rule.getClass()); 123 methodInvoker.setTargetMethod(methodName); 124 125 Object[] arguments = new Object[1]; 126 arguments[0] = event; 127 methodInvoker.setArguments(arguments); 128 129 // invoke rule method 130 try { 131 LOG.debug("Invoking rule method: " + methodInvoker.getTargetMethod() + " for class: " + rule.getClass() 132 .getName()); 133 methodInvoker.prepare(); 134 135 Class<?> methodReturnType = methodInvoker.getPreparedMethod().getReturnType(); 136 if (StringUtils.equals("void", methodReturnType.getName())) { 137 methodInvoker.invoke(); 138 } else { 139 success &= (Boolean) methodInvoker.invoke(); 140 } 141 } catch (Exception e) { 142 LOG.error("Error invoking rule method for class: " + rule.getClass().getName(), e); 143 throw new RuntimeException("Error invoking rule method for class: " + rule.getClass().getName(), e); 144 } 145 146 return success; 147 } 148 149 /** 150 * Builds a list containing AddAdHocRoutePersonEvents since the validation done for an AdHocRouteRecipient is the 151 * same for all events 152 * 153 * @see org.kuali.rice.krad.service.KualiRuleService#generateAdHocRoutePersonEvents(org.kuali.rice.krad.document.Document) 154 */ 155 public List<AddAdHocRoutePersonEvent> generateAdHocRoutePersonEvents(Document document) { 156 List<AdHocRoutePerson> adHocRoutePersons = document.getAdHocRoutePersons(); 157 158 List<AddAdHocRoutePersonEvent> events = new ArrayList<AddAdHocRoutePersonEvent>(); 159 160 for (int i = 0; i < adHocRoutePersons.size(); i++) { 161 events.add(new AddAdHocRoutePersonEvent( 162 KRADConstants.EXISTING_AD_HOC_ROUTE_PERSON_PROPERTY_NAME + "[" + i + "]", document, 163 adHocRoutePersons.get(i))); 164 } 165 166 return events; 167 } 168 169 /** 170 * Builds a list containing AddAdHocRoutePersonEvents since the validation done for an AdHocRouteRecipient is the 171 * same for all events 172 * 173 * @see org.kuali.rice.krad.service.KualiRuleService#generateAdHocRouteWorkgroupEvents(org.kuali.rice.krad.document.Document) 174 */ 175 public List<AddAdHocRouteWorkgroupEvent> generateAdHocRouteWorkgroupEvents(Document document) { 176 List<AdHocRouteWorkgroup> adHocRouteWorkgroups = document.getAdHocRouteWorkgroups(); 177 178 List<AddAdHocRouteWorkgroupEvent> events = new ArrayList<AddAdHocRouteWorkgroupEvent>(); 179 180 for (int i = 0; i < adHocRouteWorkgroups.size(); i++) { 181 events.add(new AddAdHocRouteWorkgroupEvent( 182 KRADConstants.EXISTING_AD_HOC_ROUTE_WORKGROUP_PROPERTY_NAME + "[" + i + "]", document, 183 adHocRouteWorkgroups.get(i))); 184 } 185 186 return events; 187 } 188 189 /** 190 * @param document 191 * @param ruleInterface 192 * @return instance of the businessRulesClass for the given document's type, if that businessRulesClass implements 193 * the given ruleInterface 194 */ 195 public BusinessRule getBusinessRulesInstance(Document document, Class<? extends BusinessRule> ruleInterface) { 196 // get the businessRulesClass 197 Class<? extends BusinessRule> businessRulesClass = null; 198 if (document instanceof TransactionalDocument) { 199 TransactionalDocument transactionalDocument = (TransactionalDocument) document; 200 201 businessRulesClass = getDocumentDictionaryService().getBusinessRulesClass(transactionalDocument); 202 if (businessRulesClass == null) { 203 return new TransactionalDocumentRuleBase(); // default to a generic rule that will enforce Required fields 204 } 205 } else if (document instanceof MaintenanceDocument) { 206 MaintenanceDocument maintenanceDocument = (MaintenanceDocument) document; 207 208 businessRulesClass = getDocumentDictionaryService().getBusinessRulesClass(maintenanceDocument); 209 if (businessRulesClass == null) { 210 return new MaintenanceDocumentRuleBase(); // default to a generic rule that will enforce Required fields 211 } 212 } else { 213 LOG.error("unable to get businessRulesClass for unknown document type '" 214 + document.getClass().getName() 215 + "'"); 216 } 217 218 // instantiate and return it if it implements the given ruleInterface 219 BusinessRule rule = null; 220 if (businessRulesClass != null) { 221 try { 222 if (ruleInterface.isAssignableFrom(businessRulesClass)) { 223 rule = businessRulesClass.newInstance(); 224 } 225 } catch (IllegalAccessException e) { 226 throw new RiceRuntimeException("error processing business rules", e); 227 } catch (InstantiationException e) { 228 throw new RiceRuntimeException("error processing business rules", e); 229 } 230 } 231 232 return rule; 233 } 234 235 /** 236 * Increases the registered error path, so that field highlighting can occur on the appropriate object 237 * attribute 238 * 239 * @param errorPathPrefix 240 */ 241 private void increaseErrorPath(String errorPathPrefix) { 242 MessageMap errorMap = GlobalVariables.getMessageMap(); 243 244 if (!StringUtils.isBlank(errorPathPrefix)) { 245 errorMap.addToErrorPath(errorPathPrefix); 246 } 247 } 248 249 /** 250 * Decreases the registered error path, so that field highlighting can occur on the appropriate object 251 * attribute 252 * 253 * @param errorPathPrefix 254 */ 255 private void decreaseErrorPath(String errorPathPrefix) { 256 MessageMap errorMap = GlobalVariables.getMessageMap(); 257 258 if (!StringUtils.isBlank(errorPathPrefix)) { 259 errorMap.removeFromErrorPath(errorPathPrefix); 260 } 261 } 262 263 public DocumentDictionaryService getDocumentDictionaryService() { 264 if (documentDictionaryService == null) { 265 this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService(); 266 } 267 return documentDictionaryService; 268 } 269 270 public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) { 271 this.documentDictionaryService = documentDictionaryService; 272 } 273 274 public DataDictionaryService getDataDictionaryService() { 275 return dataDictionaryService; 276 } 277 278 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { 279 this.dataDictionaryService = dataDictionaryService; 280 } 281}