View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.service.impl;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.core.api.exception.RiceRuntimeException;
21  import org.kuali.rice.krad.bo.AdHocRoutePerson;
22  import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
23  import org.kuali.rice.krad.document.Document;
24  import org.kuali.rice.krad.document.TransactionalDocument;
25  import org.kuali.rice.krad.maintenance.MaintenanceDocument;
26  import org.kuali.rice.krad.rules.MaintenanceDocumentRuleBase;
27  import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase;
28  import org.kuali.rice.krad.rules.rule.BusinessRule;
29  import org.kuali.rice.krad.rules.rule.event.AddAdHocRoutePersonEvent;
30  import org.kuali.rice.krad.rules.rule.event.AddAdHocRouteWorkgroupEvent;
31  import org.kuali.rice.krad.rules.rule.event.DocumentEvent;
32  import org.kuali.rice.krad.rules.rule.event.RuleEvent;
33  import org.kuali.rice.krad.service.DataDictionaryService;
34  import org.kuali.rice.krad.service.DocumentDictionaryService;
35  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
36  import org.kuali.rice.krad.service.KualiRuleService;
37  import org.kuali.rice.krad.uif.component.MethodInvokerConfig;
38  import org.kuali.rice.krad.util.GlobalVariables;
39  import org.kuali.rice.krad.util.KRADConstants;
40  import org.kuali.rice.krad.util.MessageMap;
41  
42  import java.util.ArrayList;
43  import java.util.List;
44  
45  /**
46   * Represents a rule evaluator for Kuali. This class is to be used for evaluating business rule checks. The class
47   * defines one method right now - applyRules() which takes in a Document and a DocumentEvent and does the proper
48   * business rule checks based on the context of the event and the document type
49   *
50   * @author Kuali Rice Team (rice.collab@kuali.org)
51   */
52  public class KualiRuleServiceImpl implements KualiRuleService {
53      private static final Logger LOG = Logger.getLogger(KualiRuleServiceImpl.class);
54  
55      private DocumentDictionaryService documentDictionaryService;
56      private DataDictionaryService dataDictionaryService;
57  
58      /**
59       * @see org.kuali.rice.krad.service.KualiRuleService#applyRules(org.kuali.rice.krad.rules.rule.event.DocumentEvent)
60       */
61      public boolean applyRules(DocumentEvent event) {
62          if (event == null) {
63              throw new IllegalArgumentException("invalid (null) event");
64          }
65  
66          event.validate();
67          if (LOG.isDebugEnabled()) {
68              LOG.debug("calling applyRules for event " + event);
69          }
70  
71          BusinessRule rule = getBusinessRulesInstance(event.getDocument(), event.getRuleInterfaceClass());
72  
73          boolean success = true;
74          if (rule != null) {
75              if (LOG.isDebugEnabled()) {
76                  LOG.debug("processing " + event.getName() + " with rule " + rule.getClass().getName());
77              }
78              increaseErrorPath(event.getErrorPathPrefix());
79  
80              // get any child events and apply rules
81              List<RuleEvent> events = event.generateEvents();
82              for (RuleEvent generatedEvent : events) {
83                  success &= applyRules((DocumentEvent) generatedEvent);
84              }
85  
86              // now call the event rule method
87              if (StringUtils.isNotBlank(event.getRuleMethodName())) {
88                  success &= invokeBusinessRuleMethod(rule, event);
89              } else {
90                  success &= event.invokeRuleMethod(rule);
91              }
92  
93              decreaseErrorPath(event.getErrorPathPrefix());
94  
95              // report failures
96              if (!success) {
97                  if (LOG.isDebugEnabled()) { // NO, this is not a type - only log if in debug mode - this is not an error in production
98                      LOG.debug(event.getName() + " businessRule " + rule.getClass().getName() + " failed");
99                  }
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 }