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.kew.rule;
17  
18  import org.apache.commons.lang.ObjectUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.log4j.Logger;
21  import org.kuali.rice.core.api.exception.RiceRuntimeException;
22  import org.kuali.rice.core.api.reflect.ObjectDefinition;
23  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
24  import org.kuali.rice.core.api.util.ClassLoaderUtils;
25  import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
26  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
27  import org.kuali.rice.kew.actionrequest.KimGroupRecipient;
28  import org.kuali.rice.kew.actionrequest.KimPrincipalRecipient;
29  import org.kuali.rice.kew.actionrequest.Recipient;
30  import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
31  import org.kuali.rice.kew.api.KewApiServiceLocator;
32  import org.kuali.rice.kew.api.action.ActionRequestStatus;
33  import org.kuali.rice.kew.api.exception.WorkflowException;
34  import org.kuali.rice.kew.api.extension.ExtensionDefinition;
35  import org.kuali.rice.kew.api.rule.RuleDelegation;
36  import org.kuali.rice.kew.api.rule.RuleResponsibility;
37  import org.kuali.rice.kew.api.rule.RuleService;
38  import org.kuali.rice.kew.api.rule.RuleTemplateAttribute;
39  import org.kuali.rice.kew.engine.RouteContext;
40  import org.kuali.rice.kew.engine.node.NodeState;
41  import org.kuali.rice.kew.engine.node.RouteNode;
42  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
43  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
44  import org.kuali.rice.kew.service.KEWServiceLocator;
45  import org.kuali.rice.kew.user.RoleRecipient;
46  import org.kuali.rice.kew.api.KewApiConstants;
47  import org.kuali.rice.kew.util.PerformanceLogger;
48  import org.kuali.rice.kew.util.ResponsibleParty;
49  import org.kuali.rice.kew.util.Utilities;
50  
51  import java.sql.Timestamp;
52  import java.util.ArrayList;
53  import java.util.List;
54  import java.util.Map;
55  
56  
57  /**
58   * Generates Action Requests for a Document using the rule system and the specified
59   * {@link org.kuali.rice.kew.rule.bo.RuleTemplateBo}.
60   *
61   * @see ActionRequestValue
62   * @see org.kuali.rice.kew.rule.bo.RuleTemplateBo
63   * @see RuleBaseValues
64   *
65   * @author Kuali Rice Team (rice.collab@kuali.org)
66   */
67  public class FlexRM {
68  
69  	private static final Logger LOG = Logger.getLogger(FlexRM.class);
70  
71  	/**
72  	 * The default type of rule selector implementation to use if none is explicitly
73  	 * specified for the node.
74  	 */
75  	public static final String DEFAULT_RULE_SELECTOR = "Template";
76  	/**
77  	 * Package in which rule selector implementations live
78  	 */
79  	private static final String RULE_SELECTOR_PACKAGE = "org.kuali.rice.kew.rule";
80  	/**
81  	 * The class name suffix all rule selectors should have; e.g. FooRuleSelector
82  	 */
83  	private static final String RULE_SELECTOR_SUFFIX= "RuleSelector";
84  
85  	private final Timestamp effectiveDate;
86  	/**
87  	 * An accumulator that keeps track of the number of rules that have been selected over the lifespan of
88  	 * this FlexRM instance.
89  	 */
90  	private int selectedRules;
91  
92  	public FlexRM() {
93  		this.effectiveDate = null;
94  	}
95  
96  	public FlexRM(Timestamp effectiveDate) {
97  		this.effectiveDate = effectiveDate;
98  	}
99  
100 	/*public List<ActionRequestValue> getActionRequests(DocumentRouteHeaderValue routeHeader, String ruleTemplateName) throws KEWUserNotFoundException, WorkflowException {
101 	return getActionRequests(routeHeader, null, ruleTemplateName);
102     }*/
103 
104 	// loads a RuleSelector implementation
105 	protected RuleSelector loadRuleSelector(RouteNode routeNodeDef, RouteNodeInstance nodeInstance) {
106 		// first see if there ruleselector is configured on a nodeinstance basis
107 		NodeState ns = null;
108 		if (nodeInstance != null) {
109 			ns = nodeInstance.getNodeState(KewApiConstants.RULE_SELECTOR_NODE_STATE_KEY);
110 		}
111 		String ruleSelectorName = null;
112 		if (ns != null) {
113 			ruleSelectorName = ns.getValue();
114 		} else {
115 			// otherwise pull it from the RouteNode definition/prototype
116 			Map<String, String> nodeCfgParams = Utilities.getKeyValueCollectionAsMap(
117 					routeNodeDef.
118 					getConfigParams());
119 			ruleSelectorName = nodeCfgParams.get(RouteNode.RULE_SELECTOR_CFG_KEY);
120 		}
121 
122 		if (ruleSelectorName == null) {
123 			ruleSelectorName = DEFAULT_RULE_SELECTOR;
124 		}
125 		ruleSelectorName = StringUtils.capitalize(ruleSelectorName);
126 
127 		// load up the rule selection implementation
128 		String className = RULE_SELECTOR_PACKAGE + "." + ruleSelectorName + RULE_SELECTOR_SUFFIX;
129 		Class<?> ruleSelectorClass;
130 		try {
131 			ruleSelectorClass = ClassLoaderUtils.getDefaultClassLoader().loadClass(className);
132 		} catch (ClassNotFoundException cnfe) {
133 			throw new IllegalStateException("Rule selector implementation '" + className + "' not found", cnfe);
134 		}
135 		if (!RuleSelector.class.isAssignableFrom(ruleSelectorClass)) {
136 			throw new IllegalStateException("Specified class '" + ruleSelectorClass + "' does not implement RuleSelector interface");
137 		}
138 		RuleSelector ruleSelector;
139 		try {
140 			ruleSelector = ((Class<RuleSelector>) ruleSelectorClass).newInstance();
141 		} catch (Exception e) {
142 			if (e instanceof RuntimeException) {
143 				throw (RuntimeException)e;
144 			}
145 			throw new IllegalStateException("Error instantiating rule selector implementation '" + ruleSelectorClass + "'", e);
146 		}
147 
148 		return ruleSelector;
149 	}
150 
151 	/**
152 	 * Generates action requests
153 	 * @param routeHeader the document route header
154 	 * @param nodeInstance the route node instance; this may NOT be null
155 	 * @param ruleTemplateName the rule template
156 	 * @return list of action requests
157 	 * @throws WorkflowException
158 	 */
159 	public List<ActionRequestValue> getActionRequests(DocumentRouteHeaderValue routeHeader, RouteNodeInstance nodeInstance, String ruleTemplateName) {
160 		return getActionRequests(routeHeader, nodeInstance.getRouteNode(), nodeInstance, ruleTemplateName);
161 	}
162 
163 	/**
164 	 * Generates action requests
165 	 * @param routeHeader the document route header
166 	 * @param routeNodeDef the RouteNode definition of the route node instance
167 	 * @param nodeInstance the route node instance; this may be null!
168 	 * @param ruleTemplateName the rule template
169 	 * @return list of action requests
170 	 * @throws WorkflowException
171 	 */
172 	public List<ActionRequestValue> getActionRequests(DocumentRouteHeaderValue routeHeader, RouteNode routeNodeDef, RouteNodeInstance nodeInstance, String ruleTemplateName) {
173 		RouteContext context = RouteContext.getCurrentRouteContext();
174 		// TODO really the route context just needs to be able to support nested create and clears
175 		// (i.e. a Stack model similar to transaction intercepting in Spring) and we wouldn't have to do this
176 		if (context.getDocument() == null) {
177 			context.setDocument(routeHeader);
178 		}
179 		if (context.getNodeInstance() == null) {
180 			context.setNodeInstance(nodeInstance);
181 		}
182 
183 		LOG.debug("Making action requests for document " + routeHeader.getDocumentId());
184 
185 		RuleSelector ruleSelector = loadRuleSelector(routeNodeDef, nodeInstance);
186 
187 		List<Rule> rules = ruleSelector.selectRules(context, routeHeader, nodeInstance, ruleTemplateName, effectiveDate);
188 
189 		// XXX: FIXME: this is a special case hack to expose info from the default selection implementation
190 		// this is used in exactly one place, RoutingReportAction, to make a distinction between no rules being
191 		// selected, and no rules actually matching when evaluated
192 		// if (numberOfRules == 0) {
193 		//   errors.add(new WorkflowServiceErrorImpl("There are no rules.", "routereport.noRules"));
194 		// } else {
195 		//   errors.add(new WorkflowServiceErrorImpl("There are rules, but no matches.", "routereport.noMatchingRules"));
196 		// }
197 		if (ruleSelector instanceof TemplateRuleSelector) {
198 			selectedRules += ((TemplateRuleSelector) ruleSelector).getNumberOfSelectedRules();
199 		}
200 
201 		PerformanceLogger performanceLogger = new PerformanceLogger();
202 
203 		ActionRequestFactory arFactory = new ActionRequestFactory(routeHeader, context.getNodeInstance());
204 
205 		List<ActionRequestValue> actionRequests = new ArrayList<ActionRequestValue>();
206 		if (rules != null) {
207 			LOG.info("Total number of rules selected by RuleSelector for documentType=" + routeHeader.getDocumentType().getName() + " and ruleTemplate=" + ruleTemplateName + ": " + rules.size());
208 			for (Rule rule: rules) {
209 				RuleExpressionResult result = rule.evaluate(rule, context);
210 				if (result.isSuccess() && result.getResponsibilities() != null) {
211 					// actionRequests.addAll(makeActionRequests(context, rule, routeHeader, null, null));
212                     org.kuali.rice.kew.api.rule.Rule ruleDef = org.kuali.rice.kew.api.rule.Rule.Builder.create(rule.getDefinition()).build();
213 					makeActionRequests(arFactory, result.getResponsibilities(), context, ruleDef, routeHeader, null, null);
214 				}
215 			}
216 		}
217 		actionRequests = new ArrayList<ActionRequestValue>(arFactory.getRequestGraphs());
218 		performanceLogger.log("Time to make action request for template " + ruleTemplateName);
219 
220 		return actionRequests;
221 	}
222 
223 	public ResponsibleParty resolveResponsibilityId(String responsibilityId) {
224 		return null;
225 	}
226 
227 	private void makeActionRequests(ActionRequestFactory arFactory, RouteContext context, org.kuali.rice.kew.api.rule.Rule rule, DocumentRouteHeaderValue routeHeader, ActionRequestValue parentRequest, RuleDelegation ruleDelegation)
228 			throws WorkflowException {
229 
230 		List<org.kuali.rice.kew.api.rule.RuleResponsibility> responsibilities = rule.getRuleResponsibilities();
231 		makeActionRequests(arFactory, responsibilities, context, rule, routeHeader, parentRequest, ruleDelegation);
232 	}
233 
234 	public void makeActionRequests(ActionRequestFactory arFactory, List<org.kuali.rice.kew.api.rule.RuleResponsibility> responsibilities, RouteContext context, org.kuali.rice.kew.api.rule.Rule rule, DocumentRouteHeaderValue routeHeader, ActionRequestValue parentRequest, RuleDelegation ruleDelegation) {
235 
236 		//	Set actionRequests = new HashSet();
237         for (org.kuali.rice.kew.api.rule.RuleResponsibility responsibility : responsibilities)
238         {
239             //	    arFactory = new ActionRequestFactory(routeHeader);
240 
241             if (responsibility.isUsingRole())
242             {
243                 makeRoleActionRequests(arFactory, context, rule, responsibility, routeHeader, parentRequest, ruleDelegation);
244             } else
245             {
246                 makeActionRequest(arFactory, context, rule, routeHeader, responsibility, parentRequest, ruleDelegation);
247             }
248             //	    if (arFactory.getRequestGraph() != null) {
249             //	    actionRequests.add(arFactory.getRequestGraph());
250             //	    }
251         }
252 	}
253 
254 	private void buildDelegationGraph(ActionRequestFactory arFactory, RouteContext context, 
255 			org.kuali.rice.kew.api.rule.Rule delegationRule, DocumentRouteHeaderValue routeHeaderValue, ActionRequestValue parentRequest, RuleDelegation ruleDelegation) {
256 		context.setActionRequest(parentRequest);
257         RuleBaseValues delRuleBo = KEWServiceLocator.getRuleService().getRuleByName(delegationRule.getName());
258 		if (delegationRule.isActive()) {
259             for (org.kuali.rice.kew.api.rule.RuleResponsibility delegationResp : delegationRule.getRuleResponsibilities())
260             {
261                 if (delegationResp.isUsingRole())
262                 {
263                     makeRoleActionRequests(arFactory, context, delegationRule, delegationResp, routeHeaderValue, parentRequest, ruleDelegation);
264                 } else if (delRuleBo.isMatch(context.getDocumentContent()))
265                 {
266                     makeActionRequest(arFactory, context, delegationRule, routeHeaderValue, delegationResp, parentRequest, ruleDelegation);
267                 }
268             }
269 		}
270 	}
271 
272 	/**
273 	 * Generates action requests for a role responsibility
274 	 */
275 	private void makeRoleActionRequests(ActionRequestFactory arFactory, RouteContext context, 
276 			org.kuali.rice.kew.api.rule.Rule rule, org.kuali.rice.kew.api.rule.RuleResponsibility resp, DocumentRouteHeaderValue routeHeader, ActionRequestValue parentRequest,
277 			RuleDelegation ruleDelegation)
278 	{
279 		String roleName = resp.getResolvedRoleName();
280 		//RoleAttribute roleAttribute = resp.resolveRoleAttribute();
281         RoleAttribute roleAttribute = null;
282         if (resp.isUsingRole()) {
283             //get correct extension definition
284             roleAttribute = (RoleAttribute) GlobalResourceLoader.getResourceLoader().getObject(new ObjectDefinition(
285                     resp.getRoleAttributeName()));
286 
287             if (roleAttribute instanceof XmlConfiguredAttribute) {
288                 ExtensionDefinition roleAttributeDefinition = null;
289                 for (RuleTemplateAttribute ruleTemplateAttribute : rule.getRuleTemplate().getRuleTemplateAttributes()) {
290                     if (resp.getRoleAttributeName().equals(ruleTemplateAttribute.getRuleAttribute().getResourceDescriptor())) {
291                         roleAttributeDefinition = ruleTemplateAttribute.getRuleAttribute();
292                         break;
293                     }
294                 }
295                 ((XmlConfiguredAttribute)roleAttribute).setExtensionDefinition(roleAttributeDefinition);
296             }
297         }
298 		//setRuleAttribute(roleAttribute, rule, resp.getRoleAttributeName());
299 		List<String> qualifiedRoleNames = new ArrayList<String>();
300 		if (parentRequest != null && parentRequest.getQualifiedRoleName() != null) {
301 			qualifiedRoleNames.add(parentRequest.getQualifiedRoleName());
302 		} else {
303 	        qualifiedRoleNames.addAll(roleAttribute.getQualifiedRoleNames(roleName, context.getDocumentContent()));
304 		}
305         for (String qualifiedRoleName : qualifiedRoleNames) {
306             if (parentRequest == null && isDuplicateActionRequestDetected(routeHeader, context.getNodeInstance(), resp, qualifiedRoleName)) {
307                 continue;
308             }
309 
310             ResolvedQualifiedRole resolvedRole = roleAttribute.resolveQualifiedRole(context, roleName, qualifiedRoleName);
311             RoleRecipient recipient = new RoleRecipient(roleName, qualifiedRoleName, resolvedRole);
312             if (parentRequest == null) {
313                 ActionRequestValue roleRequest = arFactory.addRoleRequest(recipient, resp.getActionRequestedCd(),
314                         resp.getApprovePolicy(), resp.getPriority(), resp.getResponsibilityId(), rule.isForceAction(),
315                         rule.getDescription(), rule.getId());
316 
317                 List<RuleDelegation> ruleDelegations = getRuleService().getRuleDelegationsByResponsibiltityId(resp.getResponsibilityId());
318                 if (ruleDelegations != null && !ruleDelegations.isEmpty()) {
319                     // create delegations for all the children
320                     for (ActionRequestValue request : roleRequest.getChildrenRequests()) {
321                         for (RuleDelegation childRuleDelegation : ruleDelegations) {
322                             buildDelegationGraph(arFactory, context, childRuleDelegation.getDelegationRule(), routeHeader, request, childRuleDelegation);
323                         }
324                     }
325                 }
326 
327             } else {
328                 arFactory.addDelegationRoleRequest(parentRequest, resp.getApprovePolicy(), recipient, resp.getResponsibilityId(), rule.isForceAction(), ruleDelegation.getDelegationType(), rule.getDescription(), rule.getId());
329             }
330         }
331 	}
332 
333 	/**
334 	 * Determines if the attribute has a setRuleAttribute method and then sets the value appropriately if it does.
335 	 */
336 	/*private void setRuleAttribute(RoleAttribute roleAttribute, org.kuali.rice.kew.api.rule.Rule rule, String roleAttributeName) {
337 		// look for a setRuleAttribute method on the RoleAttribute
338 		Method setRuleAttributeMethod = null;
339 		try {
340 			setRuleAttributeMethod = roleAttribute.getClass().getMethod("setExtensionDefinition", RuleAttribute.class);
341 		} catch (NoSuchMethodException e) {
342             LOG.info("method setRuleAttribute not found on " + RuleAttribute.class.getName());
343         }
344 		if (setRuleAttributeMethod == null) {
345 			return;
346 		}
347 		// find the RuleAttribute by looking through the RuleTemplate
348 		RuleTemplate ruleTemplate = rule.getRuleTemplate();
349 		if (ruleTemplate != null) {
350             for (RuleTemplateAttribute ruleTemplateAttribute : ruleTemplate.getActiveRuleTemplateAttributes())
351             {
352                 RuleAttribute ruleAttribute = ExtensionUtils.loadExtension(ruleTemplateAttribute.getRuleAttribute());
353                 if (ruleAttribute.getResourceDescriptor().equals(roleAttributeName))
354                 {
355                     // this is our RuleAttribute!
356                     try
357                     {
358                         setRuleAttributeMethod.invoke(roleAttribute, ruleAttribute);
359                         break;
360                     } catch (Exception e)
361                     {
362                         throw new WorkflowRuntimeException("Failed to set ExtensionDefinition on our RoleAttribute!", e);
363                     }
364                 }
365             }
366 		}
367 	}*/
368 
369 	/**
370 	 * Generates action requests for a non-role responsibility, either a user or workgroup
371      * @throws org.kuali.rice.kew.api.exception.WorkflowException
372      */
373 	private void makeActionRequest(ActionRequestFactory arFactory, RouteContext context, org.kuali.rice.kew.api.rule.Rule rule, DocumentRouteHeaderValue routeHeader, org.kuali.rice.kew.api.rule.RuleResponsibility resp, ActionRequestValue parentRequest,
374 			RuleDelegation ruleDelegation) {
375 		if (parentRequest == null && isDuplicateActionRequestDetected(routeHeader, context.getNodeInstance(), resp, null)) {
376 			return;
377 		}
378 		Recipient recipient;
379 		if (resp.isUsingPrincipal()) {
380 	        recipient = new KimPrincipalRecipient(resp.getPrincipalId());
381         } else if (resp.isUsingGroup()) {
382             recipient = new KimGroupRecipient(resp.getGroupId());
383         } else {
384             throw new RiceRuntimeException("Illegal rule responsibility type encountered");
385         }
386 		ActionRequestValue actionRequest;
387 		if (parentRequest == null) {
388 			actionRequest = arFactory.addRootActionRequest(resp.getActionRequestedCd(),
389 					resp.getPriority(),
390 					recipient,
391 					rule.getDescription(),
392 					resp.getResponsibilityId(),
393 					rule.isForceAction(),
394 					resp.getApprovePolicy(),
395 					rule.getId());
396 
397 			List<RuleDelegation> ruleDelegations = getRuleService().getRuleDelegationsByResponsibiltityId(
398                     resp.getResponsibilityId());
399 			if (ruleDelegations != null && !ruleDelegations.isEmpty()) {
400 				for (RuleDelegation childRuleDelegation : ruleDelegations) {
401 					buildDelegationGraph(arFactory, context, childRuleDelegation.getDelegationRule(), routeHeader, actionRequest, childRuleDelegation);
402 				}
403 			}
404 			
405 		} else {
406 			arFactory.addDelegationRequest(parentRequest, recipient, resp.getResponsibilityId(), rule.isForceAction(), ruleDelegation.getDelegationType(), rule.getDescription(), rule.getId());
407 		}
408 	}
409 
410 	private boolean isDuplicateActionRequestDetected(DocumentRouteHeaderValue routeHeader, RouteNodeInstance nodeInstance, org.kuali.rice.kew.api.rule.RuleResponsibility resp, String qualifiedRoleName) {
411 		List<ActionRequestValue> requests = getActionRequestService().findByStatusAndDocId(ActionRequestStatus.DONE.getCode(), routeHeader.getDocumentId());
412         for (ActionRequestValue request : requests)
413         {
414             if (((nodeInstance != null
415                     && request.getNodeInstance() != null
416                     && request.getNodeInstance().getRouteNodeInstanceId().equals(nodeInstance.getRouteNodeInstanceId())
417                  ) || request.getRouteLevel().equals(routeHeader.getDocRouteLevel())
418                 )
419                     && request.getResponsibilityId().equals(resp.getResponsibilityId())
420                         && ObjectUtils.equals(request.getQualifiedRoleName(), qualifiedRoleName)) {
421                 return true;
422             }
423         }
424 		return false;
425 	}
426 
427 	public RuleService getRuleService() {
428 		return KewApiServiceLocator.getRuleService();
429 	}
430 
431 	private ActionRequestService getActionRequestService() {
432 		return KEWServiceLocator.getActionRequestService();
433 	}
434 
435 	public int getNumberOfMatchingRules() {
436 		return selectedRules;
437 	}
438 
439 }